Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
home:mrbadguy:1C
postgresql13
00001-1C-FULL.patch
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File 00001-1C-FULL.patch of Package postgresql13
diff --git a/contrib/Makefile b/contrib/Makefile index 1846d415b6f..aff18f93481 100644 --- a/contrib/Makefile +++ b/contrib/Makefile @@ -48,7 +48,13 @@ SUBDIRS = \ tsm_system_rows \ tsm_system_time \ unaccent \ - vacuumlo + vacuumlo \ + mchar \ + fulleq \ + fasttrun \ + online_analyze \ + plantuner \ + dbcopies_decoding ifeq ($(with_openssl),yes) SUBDIRS += sslinfo diff --git a/contrib/auto_explain/auto_explain.c b/contrib/auto_explain/auto_explain.c index dcff9cba48c..a218852a74d 100644 --- a/contrib/auto_explain/auto_explain.c +++ b/contrib/auto_explain/auto_explain.c @@ -18,6 +18,7 @@ #include "commands/explain.h" #include "executor/instrument.h" #include "jit/jit.h" +#include "optimizer/planner.h" #include "utils/guc.h" PG_MODULE_MAGIC; @@ -391,6 +392,7 @@ explain_ExecutorEnd(QueryDesc *queryDesc) if (msec >= auto_explain_log_min_duration) { ExplainState *es = NewExplainState(); + instr_time planduration = queryDesc->plannedstmt->planDuration; es->analyze = (queryDesc->instrument_options && auto_explain_log_analyze); es->verbose = auto_explain_log_verbose; @@ -428,8 +430,9 @@ explain_ExecutorEnd(QueryDesc *queryDesc) * often result in duplication. */ ereport(auto_explain_log_level, - (errmsg("duration: %.3f ms plan:\n%s", - msec, es->str->data), + (errmsg("duration: %.3f ms planning: %.3f ms plan:\n%s", + msec, 1000 * INSTR_TIME_GET_DOUBLE(planduration), + es->str->data), errhidestmt(true))); } diff --git a/contrib/dbcopies_decoding/Makefile b/contrib/dbcopies_decoding/Makefile new file mode 100644 index 00000000000..edb533292c3 --- /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 00000000000..785d400e3c9 --- /dev/null +++ b/contrib/dbcopies_decoding/dbcopies_decoding.c @@ -0,0 +1,898 @@ +#include "postgres.h" + +#include "catalog/pg_type.h" + +#include "replication/logical.h" +#include "replication/origin.h" + +#include "utils/builtins.h" +#include "utils/lsyscache.h" +#include "utils/memutils.h" +#include "utils/rel.h" +#include "utils/syscache.h" +#include "utils/catcache.h" +#include "utils/timestamp.h" +#include "utils/cash.h" +#include "utils/pg_locale.h" +#include "utils/date.h" +#include "utils/datetime.h" + +#include "mchar.h" + +PG_MODULE_MAGIC; + + +Oid MCHAROID = InvalidOid; +Oid MVARCHAROID = InvalidOid; +const char cQuoteChar = '\''; +const char cContinueChar = '!'; + +extern PGDLLEXPORT void _PG_init(void); +extern PGDLLEXPORT void _PG_output_plugin_init(OutputPluginCallbacks* cb); + +typedef struct +{ + MemoryContext context; + int record_buf_size; + //Заказанный размер записи + int prepare_header_size; + //Размер заголовка, который записывает OutputPluginPrepareWrite в ctx->out + bool include_xids; + //флаг Записывать идентификатор транзакции + bool skip_change; + //флаг пропустить все, ничего не выводить + bool xact_wrote_changes; + //Признак того, что старт транзакции уже выведен. +} DecodingData; + +static void decode_startup(LogicalDecodingContext* ctx, + OutputPluginOptions* opt, bool is_init); +static void decode_shutdown(LogicalDecodingContext* ctx + ); +static void decode_begin_txn(LogicalDecodingContext* ctx, + ReorderBufferTXN* txn); +static void decode_commit_txn(LogicalDecodingContext* ctx, + ReorderBufferTXN* txn, XLogRecPtr commit_lsn); +static void decode_change(LogicalDecodingContext* ctx, + ReorderBufferTXN* txn, Relation rel, ReorderBufferChange* change); +static void decode_truncate(LogicalDecodingContext* ctx, + ReorderBufferTXN* txn, int nrelations, Relation relations[], + ReorderBufferChange* change); +static bool filter_by_origin(LogicalDecodingContext *ctx, + RepOriginId origin_id); + +#ifndef U8_TRUNCATE_IF_INCOMPLETE +// © 2016 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html +/* +******************************************************************************* +* +* Copyright (C) 1999-2015, International Business Machines +* Corporation and others. All Rights Reserved. +* +******************************************************************************* +* file name: utf8.h +* encoding: UTF-8 +* tab size: 8 (not used) +* indentation:4 +* +* created on: 1999sep13 +* created by: Markus W. Scherer +*/ +#define U8_LEAD4_T1_BITS \ +"\x00\x00\x00\x00\x00\x00\x00\x00\x1E\x0F\x0F\x0F\x00\x00\x00\x00" +#define U8_IS_VALID_LEAD4_AND_T1(lead, t1) \ +(U8_LEAD4_T1_BITS[(uint8_t)(t1)>>4]&(1<<((lead)&7))) +#define U8_LEAD3_T1_BITS \ +"\x20\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x10\x30\x30" +#define U8_IS_VALID_LEAD3_AND_T1(lead, t1) \ +(U8_LEAD3_T1_BITS[(lead)&0xf]&(1<<((uint8_t)(t1)>>5))) +#define U8_TRUNCATE_IF_INCOMPLETE(s, start, length) { \ + if((length)>(start)) { \ + uint8_t __b1=s[(length)-1]; \ + if(U8_IS_SINGLE(__b1)) { \ + /* common ASCII character */ \ + } else if(U8_IS_LEAD(__b1)) { \ + --(length); \ + } else if(U8_IS_TRAIL(__b1) && ((length)-2)>=(start)) { \ + uint8_t __b2=s[(length)-2]; \ + if(0xe0<=__b2 && __b2<=0xf4) { \ + if(__b2<0xf0 ? U8_IS_VALID_LEAD3_AND_T1(__b2, __b1) : \ + U8_IS_VALID_LEAD4_AND_T1(__b2, __b1)) { \ + (length)-=2; \ + } \ + } else if(U8_IS_TRAIL(__b2) && ((length)-3)>=(start)) { \ + uint8_t __b3=s[(length)-3]; \ + if(0xf0<=__b3 && __b3<=0xf4 && \ + U8_IS_VALID_LEAD4_AND_T1(__b3, __b2)) { \ + (length)-=3; \ + } \ + } \ + } \ + } \ +} + +#endif + +void _PG_init(void) +{ +} + +void _PG_output_plugin_init(OutputPluginCallbacks* cb) +{ + AssertVariableIsOfType(&_PG_output_plugin_init, LogicalOutputPluginInit); + + cb->startup_cb = decode_startup; + cb->begin_cb = decode_begin_txn; + cb->change_cb = decode_change; + cb->truncate_cb = decode_truncate; + cb->commit_cb = decode_commit_txn; + cb->shutdown_cb = decode_shutdown; + cb->filter_by_origin_cb = filter_by_origin; +} + +static bool tryExtractBoolOption(DefElem* elem, const char* name, bool* dest) +{ + if (strcmp(elem->defname, name) == 0) + { + if (elem->arg != NULL) + { + if (!parse_bool(strVal(elem->arg), dest)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("could not parse value \"%s\" for parameter \"%s\"", + strVal(elem->arg), elem->defname))); + } + return true; + } + else + return false; +} + +static bool tryExtractIntOption(DefElem* elem, const char* name, int32* dest) +{ + if (strcmp(elem->defname, name) == 0) + { + if (elem->arg != NULL) + *dest = pg_strtoint32(strVal(elem->arg)); + return true; + } + else + return false; +} + +static void readTypeOID(char* typeName, Oid* typeOid) +{ + if (*typeOid == InvalidOid) + { + CatCList* catlist = SearchSysCacheList(TYPENAMENSP, + 1, CStringGetDatum(typeName), 0, 0); + if (catlist->n_members == 1) + *typeOid = ((Form_pg_type)GETSTRUCT( + &catlist->members[0]->tuple))->oid; + ReleaseSysCacheList(catlist); + + if (*typeOid == InvalidOid) + elog(WARNING, "OID of type %s not defined!", typeName); + } +} + +static void decode_startup(LogicalDecodingContext* ctx, + OutputPluginOptions* opt, bool is_init) +{ + ListCell* option; + DecodingData* data = palloc0(sizeof(DecodingData)); + + data->include_xids = true; + data->skip_change = false; + data->record_buf_size = ALLOCSET_DEFAULT_MAXSIZE / 4; + foreach(option, ctx->output_plugin_options) + { + DefElem* elem = lfirst(option); + Assert(elem->arg == NULL || IsA(elem->arg, String)); + + if (!tryExtractBoolOption(elem, "include-xids", + &data->include_xids)) + if (!tryExtractBoolOption(elem, "skip-change", + &data->skip_change)) + if (!tryExtractIntOption(elem, "slice_size", + &data->record_buf_size)) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("option \"%s\" = \"%s\" is unknown", + elem->defname, + elem->arg ? strVal(elem->arg) : "(null)") + ) + ); + } + } + data->context = AllocSetContextCreate(ctx->context, + "text conversion context", + ALLOCSET_DEFAULT_SIZES); + ctx->output_plugin_private = data; + + opt->output_type = OUTPUT_PLUGIN_TEXTUAL_OUTPUT; + opt->receive_rewrites = false; +} + +static void decode_shutdown(LogicalDecodingContext* ctx) +{ + DecodingData* data = ctx->output_plugin_private; + + MemoryContextDelete(data->context); +} + +static bool filter_by_origin(LogicalDecodingContext *ctx, + RepOriginId origin_id) +{ + DecodingData* data = ctx->output_plugin_private; + return data && data->skip_change; +} + +static void decode_begin_txn(LogicalDecodingContext* ctx, + ReorderBufferTXN* txn) +{ + DecodingData* data = ctx->output_plugin_private; + + data->xact_wrote_changes = false; +} + +static void decode_commit_txn(LogicalDecodingContext* ctx, + ReorderBufferTXN* txn, XLogRecPtr commit_lsn) +{ + DecodingData* data = ctx->output_plugin_private; + + if (!data->xact_wrote_changes || data->skip_change) + return; + + OutputPluginPrepareWrite(ctx, true); + if (data->include_xids) + appendStringInfo(ctx->out, "C %u", txn->xid); + else + appendStringInfo(ctx->out, "C"); + OutputPluginWrite(ctx, true); +} + +static int record_buf_size(LogicalDecodingContext* ctx) { + return ((DecodingData*)(ctx->output_plugin_private))->record_buf_size; +} + +static void prepareFlushedCtx(LogicalDecodingContext* ctx) +{ + ctx->out->len = 0; + OutputPluginPrepareWrite(ctx, true); + ((DecodingData*)(ctx->output_plugin_private) + )->prepare_header_size = ctx->out->len; +} + +static int checkFlushCtx(LogicalDecodingContext* ctx, int toWriteSize) +{ //возвращает максимальное число байт, + //которое можно записать до превышения лимита длинны + int overflowRemain = record_buf_size(ctx) - ctx->out->len - 1; + if (overflowRemain <= toWriteSize) + { + appendStringInfoChar(ctx->out, cContinueChar); + switch (((DecodingData*)(ctx->output_plugin_private) + )->prepare_header_size) + { + case 0: + break; + case 1 + sizeof(int64) * 3: + memset(&ctx->out->data[1], 0, sizeof(int64) * 2); + break; + default: + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("Unsupported ctx->prepare_write function!"))); + + } + OutputPluginWrite(ctx, false); + prepareFlushedCtx(ctx); + return record_buf_size(ctx) - ctx->out->len - 1; + } + else + return overflowRemain; +} + +static void printByts(LogicalDecodingContext* ctx, Datum val) +{ + const char n[] = { "0123456789abcdef" }; + const int cDig = 16; + char* bytsData = VARDATA(val); + int32 bytsLen = VARSIZE_ANY_EXHDR(val); + int resultSize = 3 + bytsLen * 2 + 1; //остаток, который требуется записать + int overflowRemain = checkFlushCtx(ctx, 3); + if (resultSize > overflowRemain) + enlargeStringInfo(ctx->out, overflowRemain); + else + enlargeStringInfo(ctx->out, resultSize); + + appendStringInfoString(ctx->out, "\'\\x"); + overflowRemain -= 3; + resultSize -= 3; + + { + int32 i; + for (i = 0; i < bytsLen; ++i) + { + int x; + if (overflowRemain < 2) + { + overflowRemain = checkFlushCtx(ctx, 2); + if (resultSize > overflowRemain) + enlargeStringInfo(ctx->out, overflowRemain); + else + enlargeStringInfo(ctx->out, resultSize); + } + x = bytsData[i] & 255; + ctx->out->data[ctx->out->len] = n[x / cDig]; + ctx->out->data[ctx->out->len + 1] = n[x % cDig]; + ctx->out->len += 2; + overflowRemain -= 2; + resultSize -= 2; + } + } + if (overflowRemain < 1) + checkFlushCtx(ctx, 1); + appendStringInfoChar(ctx->out, cQuoteChar); +} + +static bool truncateIfIncmoplete(const int maxCharSize, const char* str, int* len) +{ + if (maxCharSize == 1) + return true; + else + { + int dbEnc = GetDatabaseEncoding(); + if (dbEnc == PG_UTF8) + { + U8_TRUNCATE_IF_INCOMPLETE(str, 0, *len); + return (len > 0); + } + else + { //медленный экзотичный вариант + int truncCount; + for (truncCount = 1; truncCount < maxCharSize; ++truncCount) + { + int charLen; + for (charLen = 1; + charLen <= maxCharSize && *len >= charLen; + ++charLen) + if (pg_verify_mbstr(dbEnc, &str[*len - charLen], + charLen, true)) + return true; + + --(*len); + } + } + } + + return false; +} + +static void printCharVarchar(LogicalDecodingContext* ctx, Datum val) +{ + const int maxCharSize = pg_database_encoding_max_length(); + char* bytsData = VARDATA(val); + int32 bytsLen = VARSIZE_ANY_EXHDR(val); + int overflowRemain = checkFlushCtx(ctx, 1 + maxCharSize); + + appendStringInfoChar(ctx->out, cQuoteChar); + --overflowRemain; + { + char* pBegin = bytsData; + int L = 0; + int i; + for (i = 0; i < bytsLen; ++i) + { + bool overflow; + ++L; + overflow = (L >= overflowRemain); + if (bytsData[i] == cQuoteChar || overflow || i + 1 == bytsLen) + { + if (overflow && + !(bytsData[i] == cQuoteChar || i + 1 == bytsLen)) + { + if (!truncateIfIncmoplete(maxCharSize, pBegin, &L)) + + ereport(ERROR, + (errcode(ERRCODE_INVALID_CHARACTER_VALUE_FOR_CAST), + errmsg("invalid string value") + ) + ); + } + appendBinaryStringInfo(ctx->out, pBegin, L); + pBegin += L; + if (bytsData[i] == cQuoteChar) + { + overflowRemain = checkFlushCtx(ctx, maxCharSize+1); + appendStringInfoChar(ctx->out, bytsData[i]); + --overflowRemain; + } + else if (overflow) //гарантированный сброс буфера + overflowRemain = checkFlushCtx(ctx, record_buf_size(ctx)); + else + overflowRemain = checkFlushCtx(ctx, maxCharSize); + L = 0; + } + + } + } + if (overflowRemain < 1) + checkFlushCtx(ctx, 1); + appendStringInfoChar(ctx->out, cQuoteChar); +} + +static int printM(const UChar* wordsData, + int wordsLen, LogicalDecodingContext* ctx) +{ + const int maxCharSize = pg_database_encoding_max_length(); + const UChar cQuoteUChar = L'\''; + int overflowRemain = checkFlushCtx(ctx, 1 + maxCharSize); + + appendStringInfoChar(ctx->out, cQuoteChar); + --overflowRemain; + { + const UChar* pBegin = wordsData; + int L = 0; + int i; + for (i = 0; i < wordsLen; ++i) + { + bool overflow; + ++L; + overflow = (L*maxCharSize >= overflowRemain); + if (wordsData[i] == cQuoteUChar || + overflow || + i + 1 == wordsLen) + { + if (overflow && + !(wordsData[i] == cQuoteUChar || i + 1 == wordsLen)) + { + if (U16_IS_LEAD(wordsData[i])) + --L; + + if (L == 0 || (i > 0 && U16_IS_LEAD(wordsData[i - 1]))) + ereport(ERROR, + (errcode(ERRCODE_INVALID_CHARACTER_VALUE_FOR_CAST), + errmsg("invalid utf16 string value") + ) + ); + } + enlargeStringInfo(ctx->out, L * maxCharSize); + ctx->out->len += UChar2Char(pBegin, L, + &ctx->out->data[ctx->out->len]); + pBegin += L; + + if (wordsData[i] == cQuoteUChar) + { + overflowRemain = checkFlushCtx(ctx, maxCharSize+1); + appendStringInfoChar(ctx->out, cQuoteChar); + --overflowRemain; + } + else if (overflow) + overflowRemain = checkFlushCtx(ctx, record_buf_size(ctx)); + else + overflowRemain = checkFlushCtx(ctx, maxCharSize); + L = 0; + } + } + } + return overflowRemain; +} + +static void printMVarchar(LogicalDecodingContext* ctx, Datum val) +{ + const UChar* pBegin = (UChar*)(DatumGetPointer(val) + MVARCHARHDRSZ); + if (printM(pBegin, UVARCHARLENGTH(val), ctx) < 1) + checkFlushCtx(ctx, 1); + appendStringInfoChar(ctx->out, cQuoteChar); +} + +static void printMChar(LogicalDecodingContext* ctx, Datum val) +{ + const UChar* pBegin = (UChar*)(DatumGetPointer(val) + MCHARHDRSZ); + int32 trailBlanksCount = + DatumGetMChar(val)->typmod - u_countChar32(pBegin, UCHARLENGTH(val)); + int overflowRemain = printM(pBegin, UCHARLENGTH(val), ctx); + while (trailBlanksCount > 0) + { + + if (trailBlanksCount > overflowRemain) + { + appendStringInfoSpaces(ctx->out, overflowRemain); + trailBlanksCount -= overflowRemain; + overflowRemain = checkFlushCtx(ctx, 1); + } + else + { + appendStringInfoSpaces(ctx->out, trailBlanksCount); + overflowRemain -= trailBlanksCount; + trailBlanksCount = 0; + } + } + if (overflowRemain < 1) + checkFlushCtx(ctx, 1); + appendStringInfoChar(ctx->out, cQuoteChar); +} + +static void appendCtxString(LogicalDecodingContext* ctx, char* str) +{ + int l = strlen(str); + checkFlushCtx(ctx, l); + appendBinaryStringInfo(ctx->out, str, l); +} + +static void printTimestamp(LogicalDecodingContext* ctx, Datum val) +{ + Timestamp ts = DatumGetTimestamp(val); + if (!TIMESTAMP_NOT_FINITE(ts)) + { + struct pg_tm tm; + fsec_t fsec; + if (timestamp2tm(ts, NULL, &tm, &fsec, NULL, NULL) == 0) + { //отсутствие в параметрах указателя на tz + //приводит к конвертации часов (ts with timezone) + //например было 10:23:54.123+02 получим 08:23:54 + char* str; + checkFlushCtx(ctx, 14); + enlargeStringInfo(ctx->out, 14); + str = ctx->out->data + ctx->out->len; + ctx->out->len += 14; + str = pg_ultostr_zeropad(str, + (tm.tm_year > 0) ? tm.tm_year : -(tm.tm_year - 1), 4); + str = pg_ultostr_zeropad(str, tm.tm_mon, 2); + str = pg_ultostr_zeropad(str, tm.tm_mday, 2); + str = pg_ultostr_zeropad(str, tm.tm_hour, 2); + str = pg_ultostr_zeropad(str, tm.tm_min, 2); + str = pg_ultostr_zeropad(str, Abs(tm.tm_sec), 2); + return; + } + } + appendCtxString(ctx, "'invalid timestamp'"); +} + +static void printDate(LogicalDecodingContext* ctx, Datum val) +{ + DateADT d = DatumGetDateADT(val); + if (!DATE_NOT_FINITE(d)) + { + char* str; + int year, mon, day; + j2date(d + POSTGRES_EPOCH_JDATE, &year, &mon, &day); + checkFlushCtx(ctx, 8); + enlargeStringInfo(ctx->out, 8); + str = ctx->out->data + ctx->out->len; + ctx->out->len += 8; + str = pg_ultostr_zeropad(str, (year > 0) ? year : -(year - 1), 4); + str = pg_ultostr_zeropad(str, mon, 2); + str = pg_ultostr_zeropad(str, day, 2); + return; + } + appendCtxString(ctx, "'invalid date'"); +} + +static void printTime(LogicalDecodingContext* ctx, Datum val) +{ + TimeADT t = DatumGetTimeADT(val); + char* str; + struct pg_tm tm; + fsec_t fsec; + time2tm(t, &tm, &fsec); + checkFlushCtx(ctx, 14); + enlargeStringInfo(ctx->out, 14); + str = ctx->out->data + ctx->out->len; + ctx->out->len += 14; + str = pg_ultostr_zeropad(str, 0, 4); + str = pg_ultostr_zeropad(str, 0, 2); + str = pg_ultostr_zeropad(str, 0, 2); + str = pg_ultostr_zeropad(str, tm.tm_hour, 2); + str = pg_ultostr_zeropad(str, tm.tm_min, 2); + str = pg_ultostr_zeropad(str, Abs(tm.tm_sec), 2); +} + +static void printMoney(LogicalDecodingContext* ctx, Datum val) +{ + Cash v = DatumGetCash(val); + char buf[128]; + char* pBuf = &buf[127]; + bool minus = (v < 0); + struct lconv *lconvert = PGLC_localeconv(); + int points = lconvert->frac_digits; + + if (points < 0 || points > 10) + points = 2; + + buf[127] = 0; + if (minus) + v = -v; + + do { + *(--pBuf) = ((uint64)v % 10) + '0'; + --points; + + if (points == 0) + *(--pBuf) = '.'; + + if (v) + v = ((uint64)v) / 10; + } while (v || points >= 0); + if (minus) + *(--pBuf) = '-'; + + appendCtxString(ctx, pBuf); +} + +static void printBool(LogicalDecodingContext* ctx, Datum val) +{ + appendCtxString(ctx, DatumGetBool(val) ? "true" : "false"); +} + +static void printDefault(LogicalDecodingContext* ctx, + Datum val, Oid typid, Oid typoutput) +{ // Вывод с помощью стандартной OUTPUT функции ..._out + char* dataAsChar = OidOutputFunctionCall(typoutput, val); + switch (typid) + { + case INT2OID: + case INT4OID: + case INT8OID: + case OIDOID: + case FLOAT4OID: + case FLOAT8OID: + case NUMERICOID: + appendCtxString(ctx, dataAsChar); + break; + + case BITOID: + case VARBITOID: + checkFlushCtx(ctx, (int)strlen(dataAsChar) + 3); + appendStringInfo(ctx->out, "B'%s'", dataAsChar); + break; + + default: + { + const int maxCharSize = pg_database_encoding_max_length(); + const char* pBegin; + const char* pEnd = dataAsChar; + int overflowRemain = checkFlushCtx(ctx, maxCharSize + 1); + + appendStringInfoChar(ctx->out, cQuoteChar); + --overflowRemain; + //в отличие от printCharVarchar, + //здесь я не знаю длинну, но точно знаю, что на конце ноль + for (pBegin = dataAsChar; *pBegin; pBegin = pEnd) + { + bool overflow; + while (*pEnd && + *pEnd != cQuoteChar && + (int)(pEnd - pBegin) < overflowRemain) + ++pEnd; + overflow = (int)(pEnd - pBegin) >= overflowRemain; + if (pEnd != pBegin) + { + if (overflow && *pEnd && *pEnd != cQuoteChar) + { + int32 L = (int32)(pEnd - pBegin); + if (!truncateIfIncmoplete(maxCharSize, pBegin, &L)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_CHARACTER_VALUE_FOR_CAST), + errmsg("invalid string value") + )); + pEnd = pBegin + L; + } + appendBinaryStringInfo(ctx->out, + pBegin, (int)(pEnd - pBegin)); + } + + if (*pEnd == cQuoteChar) + { + overflowRemain = checkFlushCtx(ctx, maxCharSize + 2); + appendStringInfoChar(ctx->out, *pEnd); + appendStringInfoChar(ctx->out, *pEnd); + ++pEnd; + overflowRemain -= 2; + } + else if (overflow) + overflowRemain = checkFlushCtx(ctx, record_buf_size(ctx)); + else + overflowRemain = checkFlushCtx(ctx, maxCharSize); + } + if (overflowRemain < 1) + checkFlushCtx(ctx, 1); + appendStringInfoChar(ctx->out, cQuoteChar); + } + break; + } + pfree(dataAsChar); +} + +static void printTuple(LogicalDecodingContext* ctx, + TupleDesc tupdesc, ReorderBufferTupleBuf* tuple, + bool skip_nulls, char* tableName) +{ + + if (tuple == NULL) + appendCtxString(ctx, " (no-tuple-data)"); + else + { + int natt; + for (natt = 0; natt < tupdesc->natts; natt++) + { + bool typisvarlena; + Oid typoutput; + Form_pg_attribute attr = TupleDescAttr(tupdesc, natt); + Oid typid = attr->atttypid; + bool isnull; + Datum origval; + + if (attr->attisdropped) + continue; + if (attr->attnum < 0) // Don't print system columns, + continue;//oid will already have been printed if present. + + origval = heap_getattr(&tuple->tuple, natt + 1, tupdesc, &isnull); + if (isnull) + { + if (!skip_nulls) + appendCtxString(ctx, " null"); + continue; + } + + checkFlushCtx(ctx, 1); + appendStringInfoChar(ctx->out, ' '); + + getTypeOutputInfo(typid, &typoutput, &typisvarlena); + + if (typisvarlena) + { + if (VARATT_IS_EXTERNAL_ONDISK(origval)) + appendCtxString(ctx, "unchanged-toast-datum"); + else + { + Datum val = PointerGetDatum(PG_DETOAST_DATUM(origval)); + + if (typid == BPCHAROID || + typid == VARCHAROID || + typid == TEXTOID) + printCharVarchar(ctx, val); + else if (typid == BYTEAOID) + printByts(ctx, val); + else if (typid > FirstNormalObjectId) { + readTypeOID("mchar", &MCHAROID); + readTypeOID("mvarchar", &MVARCHAROID); + + if (typid == MCHAROID) + printMChar(ctx, val); + else if (typid == MVARCHAROID) + printMVarchar(ctx, val); + else + printDefault(ctx, val, typid, typoutput); + } else + printDefault(ctx, val, typid, typoutput); + + if (DatumGetPointer(val) != DatumGetPointer(origval)) + pfree(DatumGetPointer(val)); + } + + } + else + { + switch (typid) + { + case CASHOID: + printMoney(ctx, origval); + break; + case TIMESTAMPOID: + case TIMESTAMPTZOID: + printTimestamp(ctx, origval); + break; + case DATEOID: + printDate(ctx, origval); + break; + case TIMEOID: + printTime(ctx, origval); + break; + case BOOLOID: + printBool(ctx, origval); + break; + default: + printDefault(ctx, origval, typid, typoutput); + break; + } + } + } + } +} + +static void printTransaction(DecodingData* data, + LogicalDecodingContext* ctx, ReorderBufferTXN* txn) +{ + if (data->xact_wrote_changes) + return; + + OutputPluginPrepareWrite(ctx, false); + if (data->include_xids) + appendStringInfo(ctx->out, "B %u", txn->xid); + else + appendStringInfoString(ctx->out, "B"); + OutputPluginWrite(ctx, false); + data->xact_wrote_changes = true; +} + +static void decode_change(LogicalDecodingContext* ctx, + ReorderBufferTXN* txn, Relation relation, ReorderBufferChange* change) +{ + DecodingData* data = ctx->output_plugin_private; + if (data->skip_change) + return; + { + MemoryContext old = MemoryContextSwitchTo(data->context); + TupleDesc tupdesc = RelationGetDescr(relation); + char* tableName = RelationGetRelationName(relation); + + printTransaction(data, ctx, txn); + prepareFlushedCtx(ctx); + switch (change->action) + { + case REORDER_BUFFER_CHANGE_INSERT: + { + appendStringInfoString(ctx->out, "I "); + appendStringInfoString(ctx->out, tableName); + printTuple(ctx, tupdesc, change->data.tp.newtuple, false, tableName); + } + break; + case REORDER_BUFFER_CHANGE_UPDATE: + { + appendStringInfoString(ctx->out, "U "); + appendStringInfoString(ctx->out, tableName); + printTuple(ctx, tupdesc, change->data.tp.newtuple, false, tableName); + } + break; + case REORDER_BUFFER_CHANGE_DELETE: + { + appendStringInfoString(ctx->out, "D "); + appendStringInfoString(ctx->out, tableName); + printTuple(ctx, tupdesc, change->data.tp.oldtuple, true, tableName); + } + break; + default: + Assert(false); + } + MemoryContextSwitchTo(old); + } + OutputPluginWrite(ctx, true); + MemoryContextReset(data->context); +} + +static void decode_truncate(LogicalDecodingContext* ctx, + ReorderBufferTXN* txn, int nrelations, + Relation relations[], ReorderBufferChange* change) +{ + int i; + DecodingData* data = ctx->output_plugin_private; + if (data->skip_change) + return; + + printTransaction(data, ctx, txn); + { + MemoryContext old = MemoryContextSwitchTo(data->context); + + OutputPluginPrepareWrite(ctx, true); + + appendStringInfoString(ctx->out, "T "); + + for (i = 0; i < nrelations; i++) + { + if (i > 0) + appendStringInfoString(ctx->out, ", "); + + appendStringInfoString(ctx->out, + RelationGetRelationName(relations[i])); + } + + MemoryContextSwitchTo(old); + } + OutputPluginWrite(ctx, true); + MemoryContextReset(data->context); +} diff --git a/contrib/dbcopies_decoding/expected/simple.out b/contrib/dbcopies_decoding/expected/simple.out new file mode 100644 index 00000000000..4053ec1935a --- /dev/null +++ b/contrib/dbcopies_decoding/expected/simple.out @@ -0,0 +1,28 @@ +-- predictability +SET synchronous_commit = on; +CREATE TABLE replication_example(id SERIAL PRIMARY KEY, somedata int, text varchar(120)); +SELECT 'init' FROM pg_create_logical_replication_slot('dbcopies_slot', 'dbcopies_decoding'); + ?column? +---------- + init +(1 row) + +BEGIN; +INSERT INTO replication_example(somedata, text) VALUES (1, 1); +INSERT INTO replication_example(somedata, text) VALUES (1, 2); +COMMIT; +SELECT data FROM pg_logical_slot_get_changes('dbcopies_slot', NULL, NULL, 'include-xids', '0'); + data +------------------------------- + B + I replication_example 1 1 '1' + I replication_example 2 1 '2' + C +(4 rows) + +SELECT pg_drop_replication_slot('dbcopies_slot'); + pg_drop_replication_slot +-------------------------- + +(1 row) + diff --git a/contrib/dbcopies_decoding/logical.conf b/contrib/dbcopies_decoding/logical.conf new file mode 100644 index 00000000000..367f7066514 --- /dev/null +++ b/contrib/dbcopies_decoding/logical.conf @@ -0,0 +1,2 @@ +wal_level = logical +max_replication_slots = 4 diff --git a/contrib/dbcopies_decoding/sql/simple.sql b/contrib/dbcopies_decoding/sql/simple.sql new file mode 100644 index 00000000000..1e9d2f72323 --- /dev/null +++ b/contrib/dbcopies_decoding/sql/simple.sql @@ -0,0 +1,15 @@ +-- predictability +SET synchronous_commit = on; + +CREATE TABLE replication_example(id SERIAL PRIMARY KEY, somedata int, text varchar(120)); + +SELECT 'init' FROM pg_create_logical_replication_slot('dbcopies_slot', 'dbcopies_decoding'); + +BEGIN; +INSERT INTO replication_example(somedata, text) VALUES (1, 1); +INSERT INTO replication_example(somedata, text) VALUES (1, 2); +COMMIT; + +SELECT data FROM pg_logical_slot_get_changes('dbcopies_slot', NULL, NULL, 'include-xids', '0'); + +SELECT pg_drop_replication_slot('dbcopies_slot'); diff --git a/contrib/fasttrun/Makefile b/contrib/fasttrun/Makefile new file mode 100644 index 00000000000..78e92b86cbe --- /dev/null +++ b/contrib/fasttrun/Makefile @@ -0,0 +1,17 @@ +MODULE_big = fasttrun +OBJS = fasttrun.o +DATA = fasttrun--2.0.sql fasttrun--unpackaged--2.0.sql +DOCS = README.fasttrun +REGRESS = fasttrun +EXTENSION=fasttrun + + +ifdef USE_PGXS +PGXS := $(shell pg_config --pgxs) +include $(PGXS) +else +subdir = contrib/fasttrun +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/fasttrun/README.fasttrun b/contrib/fasttrun/README.fasttrun new file mode 100644 index 00000000000..b6d1b41a6d2 --- /dev/null +++ b/contrib/fasttrun/README.fasttrun @@ -0,0 +1,16 @@ +select fasttruncate('TABLE_NAME'); + +Function truncates the temporary table and doesn't grow +pg_class size. + +Warning: function isn't transaction safe! + +For tests: +create or replace function f() returns void as $$ +begin +for i in 1..1000 +loop + PERFORM fasttruncate('tt1'); +end loop; +end; +$$ language plpgsql; diff --git a/contrib/fasttrun/expected/fasttrun.out b/contrib/fasttrun/expected/fasttrun.out new file mode 100644 index 00000000000..ef64fa6400e --- /dev/null +++ b/contrib/fasttrun/expected/fasttrun.out @@ -0,0 +1,115 @@ +CREATE EXTENSION fasttrun; +create table persist ( a int ); +insert into persist values (1); +select fasttruncate('persist'); +ERROR: Relation isn't a temporary table +insert into persist values (2); +select * from persist order by a; + a +--- + 1 + 2 +(2 rows) + +create temp table temp1 (a int); +insert into temp1 values (1); +BEGIN; +create temp table temp2 (a int); +insert into temp2 values (1); +select * from temp1 order by a; + a +--- + 1 +(1 row) + +select * from temp2 order by a; + a +--- + 1 +(1 row) + +insert into temp1 (select * from generate_series(1,10000)); +insert into temp2 (select * from generate_series(1,11000)); +analyze temp2; +select relname, relpages>0, reltuples>0 from pg_class where relname in ('temp1', 'temp2') order by relname; + relname | ?column? | ?column? +---------+----------+---------- + temp1 | f | f + temp2 | t | t +(2 rows) + +select fasttruncate('temp1'); + fasttruncate +-------------- + +(1 row) + +select fasttruncate('temp2'); + fasttruncate +-------------- + +(1 row) + +insert into temp1 values (-2); +insert into temp2 values (-2); +select * from temp1 order by a; + a +---- + -2 +(1 row) + +select * from temp2 order by a; + a +---- + -2 +(1 row) + +COMMIT; +select * from temp1 order by a; + a +---- + -2 +(1 row) + +select * from temp2 order by a; + a +---- + -2 +(1 row) + +select relname, relpages>0, reltuples>0 from pg_class where relname in ('temp1', 'temp2') order by relname; + relname | ?column? | ?column? +---------+----------+---------- + temp1 | f | f + temp2 | f | f +(2 rows) + +select fasttruncate('temp1'); + fasttruncate +-------------- + +(1 row) + +select fasttruncate('temp2'); + fasttruncate +-------------- + +(1 row) + +select * from temp1 order by a; + a +--- +(0 rows) + +select * from temp2 order by a; + a +--- +(0 rows) + +select relname, relpages>0, reltuples>0 from pg_class where relname in ('temp1', 'temp2') order by relname; + relname | ?column? | ?column? +---------+----------+---------- + temp1 | f | f + temp2 | f | f +(2 rows) + diff --git a/contrib/fasttrun/fasttrun--2.0.sql b/contrib/fasttrun/fasttrun--2.0.sql new file mode 100644 index 00000000000..708c2753151 --- /dev/null +++ b/contrib/fasttrun/fasttrun--2.0.sql @@ -0,0 +1,6 @@ +\echo Use "CREATE EXTENSION fasttrun" to load this file. \quit + + +CREATE OR REPLACE FUNCTION fasttruncate(text) +RETURNS void AS 'MODULE_PATHNAME' +LANGUAGE C RETURNS NULL ON NULL INPUT VOLATILE; diff --git a/contrib/fasttrun/fasttrun--unpackaged--2.0.sql b/contrib/fasttrun/fasttrun--unpackaged--2.0.sql new file mode 100644 index 00000000000..3a071f077e1 --- /dev/null +++ b/contrib/fasttrun/fasttrun--unpackaged--2.0.sql @@ -0,0 +1,3 @@ +\echo Use "CREATE EXTENSION fasttrun FROM unpackaged" to load this file. \quit + +ALTER EXTENSION fasttrun ADD function fasttruncate(text); diff --git a/contrib/fasttrun/fasttrun.c b/contrib/fasttrun/fasttrun.c new file mode 100644 index 00000000000..0a92851c0fc --- /dev/null +++ b/contrib/fasttrun/fasttrun.c @@ -0,0 +1,85 @@ +#include "postgres.h" + +#include "access/genam.h" +#include "access/heapam.h" +#include "miscadmin.h" +#include "storage/lmgr.h" +#include "storage/bufmgr.h" +#include "catalog/namespace.h" +#include "utils/lsyscache.h" +#include "utils/builtins.h" +#include <fmgr.h> +#include <funcapi.h> +#include <access/heapam.h> +#include <catalog/pg_type.h> +#include <catalog/heap.h> +#include <commands/vacuum.h> +#include <utils/regproc.h> +#include <utils/varlena.h> + +#ifdef PG_MODULE_MAGIC +PG_MODULE_MAGIC; +#endif + +PG_FUNCTION_INFO_V1(fasttruncate); +Datum fasttruncate(PG_FUNCTION_ARGS); +Datum +fasttruncate(PG_FUNCTION_ARGS) { + text *name=PG_GETARG_TEXT_P(0); + char *relname; + List *relname_list; + RangeVar *relvar; + Oid relOid; + Relation rel; + bool makeanalyze = false; + + relname = palloc( VARSIZE(name) + 1); + memcpy(relname, VARDATA(name), VARSIZE(name)-VARHDRSZ); + relname[ VARSIZE(name)-VARHDRSZ ] = '\0'; + + relname_list = stringToQualifiedNameList(relname); + relvar = makeRangeVarFromNameList(relname_list); + relOid = RangeVarGetRelid(relvar, AccessExclusiveLock, false); + + if ( get_rel_relkind(relOid) != RELKIND_RELATION ) + elog(ERROR,"Relation isn't a ordinary table"); + + rel = table_open(relOid, NoLock); + + if ( !isTempNamespace(get_rel_namespace(relOid)) ) + elog(ERROR,"Relation isn't a temporary table"); + + heap_truncate(list_make1_oid(relOid)); + + if ( rel->rd_rel->relpages > 0 || rel->rd_rel->reltuples > 0 ) + makeanalyze = true; + + /* + * heap_truncate doesn't unlock the table, + * so we should unlock it. + */ + + table_close(rel, AccessExclusiveLock); + + if ( makeanalyze ) { + VacuumParams params; + VacuumRelation *rel; + + params.options = VACOPT_ANALYZE; + params.freeze_min_age = -1; + params.freeze_table_age = -1; + params.multixact_freeze_min_age = -1; + params.multixact_freeze_table_age = -1; + params.is_wraparound = false; + params.log_min_duration = -1; + + rel = makeNode(VacuumRelation); + rel->relation = relvar; + rel->oid = relOid; + rel->va_cols = NULL; + vacuum(list_make1(rel), ¶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 00000000000..7862c0bf8ad --- /dev/null +++ b/contrib/fasttrun/fasttrun.control @@ -0,0 +1,5 @@ +comment = 'fast transaction-unsafe truncate' +default_version = '2.0' +module_pathname = '$libdir/fasttrun' +relocatable = true +trusted = true diff --git a/contrib/fasttrun/sql/fasttrun.sql b/contrib/fasttrun/sql/fasttrun.sql new file mode 100644 index 00000000000..0e3cb6c9beb --- /dev/null +++ b/contrib/fasttrun/sql/fasttrun.sql @@ -0,0 +1,48 @@ +CREATE EXTENSION fasttrun; + +create table persist ( a int ); +insert into persist values (1); +select fasttruncate('persist'); +insert into persist values (2); +select * from persist order by a; + +create temp table temp1 (a int); +insert into temp1 values (1); + +BEGIN; + +create temp table temp2 (a int); +insert into temp2 values (1); + +select * from temp1 order by a; +select * from temp2 order by a; + +insert into temp1 (select * from generate_series(1,10000)); +insert into temp2 (select * from generate_series(1,11000)); + +analyze temp2; +select relname, relpages>0, reltuples>0 from pg_class where relname in ('temp1', 'temp2') order by relname; + +select fasttruncate('temp1'); +select fasttruncate('temp2'); + +insert into temp1 values (-2); +insert into temp2 values (-2); + +select * from temp1 order by a; +select * from temp2 order by a; + +COMMIT; + +select * from temp1 order by a; +select * from temp2 order by a; + +select relname, relpages>0, reltuples>0 from pg_class where relname in ('temp1', 'temp2') order by relname; + +select fasttruncate('temp1'); +select fasttruncate('temp2'); + +select * from temp1 order by a; +select * from temp2 order by a; + +select relname, relpages>0, reltuples>0 from pg_class where relname in ('temp1', 'temp2') order by relname; diff --git a/contrib/file_fdw/input/file_fdw.source b/contrib/file_fdw/input/file_fdw.source index 45b728eeb3d..fdd053b3914 100644 --- a/contrib/file_fdw/input/file_fdw.source +++ b/contrib/file_fdw/input/file_fdw.source @@ -51,7 +51,7 @@ CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'csv', delimiter CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'csv', delimiter '---'); -- ERROR CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'csv', quote '---'); -- ERROR CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'csv', escape '---'); -- ERROR -CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'text', delimiter '\'); -- ERROR +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'text', delimiter '\\'); -- ERROR CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'text', delimiter '.'); -- ERROR CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'text', delimiter '1'); -- ERROR CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'text', delimiter 'a'); -- ERROR @@ -65,7 +65,7 @@ CREATE FOREIGN TABLE agg_text ( a int2 CHECK (a >= 0), b float4 ) SERVER file_server -OPTIONS (format 'text', filename '@abs_srcdir@/data/agg.data', delimiter ' ', null '\N'); +OPTIONS (format 'text', filename '@abs_srcdir@/data/agg.data', delimiter ' ', null '\\N'); GRANT SELECT ON agg_text TO regress_file_fdw_user; CREATE FOREIGN TABLE agg_csv ( a int2, diff --git a/contrib/file_fdw/output/file_fdw.source b/contrib/file_fdw/output/file_fdw.source index 52b4d5f1df7..aa626be5d99 100644 --- a/contrib/file_fdw/output/file_fdw.source +++ b/contrib/file_fdw/output/file_fdw.source @@ -61,8 +61,8 @@ CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'csv', quote '--- ERROR: COPY quote must be a single one-byte character CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'csv', escape '---'); -- ERROR ERROR: COPY escape must be a single one-byte character -CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'text', delimiter '\'); -- ERROR -ERROR: COPY delimiter cannot be "\" +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'text', delimiter '\\'); -- ERROR +ERROR: COPY delimiter must be a single one-byte character CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'text', delimiter '.'); -- ERROR ERROR: COPY delimiter cannot be "." CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'text', delimiter '1'); -- ERROR @@ -81,7 +81,7 @@ CREATE FOREIGN TABLE agg_text ( a int2 CHECK (a >= 0), b float4 ) SERVER file_server -OPTIONS (format 'text', filename '@abs_srcdir@/data/agg.data', delimiter ' ', null '\N'); +OPTIONS (format 'text', filename '@abs_srcdir@/data/agg.data', delimiter ' ', null '\\N'); GRANT SELECT ON agg_text TO regress_file_fdw_user; CREATE FOREIGN TABLE agg_csv ( a int2, diff --git a/contrib/fulleq/Makefile b/contrib/fulleq/Makefile new file mode 100644 index 00000000000..de8fbfcec41 --- /dev/null +++ b/contrib/fulleq/Makefile @@ -0,0 +1,45 @@ +MODULE_big = fulleq +OBJS = fulleq.o +DOCS = README.fulleq +REGRESS = fulleq +DATA_built = fulleq--2.0.sql fulleq--unpackaged--2.0.sql +EXTENSION=fulleq + +ARGTYPE = bool bytea char name int8 int2 int4 text \ + oid xid cid oidvector float4 float8 macaddr \ + inet cidr varchar date time timestamp timestamptz \ + interval timetz + +EXTRA_CLEAN = fulleq--2.0.sql fulleq--unpackaged--2.0.sql + +ifdef USE_PGXS +PGXS := $(shell pg_config --pgxs) +include $(PGXS) +else +subdir = contrib/fulleq +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif + +all: fulleq--2.0.sql fulleq--unpackaged--2.0.sql + +fulleq--2.0.sql: fulleq.sql.in + /bin/echo '\echo Use "CREATE EXTENSION fulleq" to load this file. \quit' > $@ + for type in $(ARGTYPE); \ + do \ + sed -e "s/ARGTYPE/$$type/g" < $< >> $@; \ + done + +fulleq--unpackaged--2.0.sql: fulleq-unpackaged.sql.in + /bin/echo '\echo Use "CREATE EXTENSION fulleq FROM unpackaged" to load this file. \quit' > $@ + /bin/echo 'DROP OPERATOR CLASS IF EXISTS int2vector_fill_ops USING hash;' >> $@ + /bin/echo 'DROP OPERATOR FAMILY IF EXISTS int2vector_fill_ops USING hash;' >> $@ + /bin/echo 'DROP FUNCTION IF EXISTS fullhash_int2vector(int2vector);' >> $@ + /bin/echo 'DROP OPERATOR IF EXISTS == (int2vector, int2vector);' >> $@ + /bin/echo 'DROP FUNCTION IF EXISTS isfulleq_int2vector(int2vector, int2vector);' >> $@ + for type in $(ARGTYPE); \ + do \ + sed -e "s/ARGTYPE/$$type/g" < $< >> $@; \ + done + diff --git a/contrib/fulleq/README.fulleq b/contrib/fulleq/README.fulleq new file mode 100644 index 00000000000..93bf0cad20e --- /dev/null +++ b/contrib/fulleq/README.fulleq @@ -0,0 +1,2 @@ +Introduce operator == which returns true when +operands are equal or both are nulls. diff --git a/contrib/fulleq/expected/fulleq.out b/contrib/fulleq/expected/fulleq.out new file mode 100644 index 00000000000..452f8593432 --- /dev/null +++ b/contrib/fulleq/expected/fulleq.out @@ -0,0 +1,61 @@ +CREATE EXTENSION fulleq; +select 4::int == 4; + ?column? +---------- + t +(1 row) + +select 4::int == 5; + ?column? +---------- + f +(1 row) + +select 4::int == NULL; + ?column? +---------- + f +(1 row) + +select NULL::int == 5; + ?column? +---------- + f +(1 row) + +select NULL::int == NULL; + ?column? +---------- + t +(1 row) + +select '4'::text == '4'; + ?column? +---------- + t +(1 row) + +select '4'::text == '5'; + ?column? +---------- + f +(1 row) + +select '4'::text == NULL; + ?column? +---------- + f +(1 row) + +select NULL::text == '5'; + ?column? +---------- + f +(1 row) + +select NULL::text == NULL; + ?column? +---------- + t +(1 row) + diff --git a/contrib/fulleq/fulleq-unpackaged.sql.in b/contrib/fulleq/fulleq-unpackaged.sql.in new file mode 100644 index 00000000000..8d759d8221f --- /dev/null +++ b/contrib/fulleq/fulleq-unpackaged.sql.in @@ -0,0 +1,10 @@ +-- For ARGTYPE + +ALTER EXTENSION fulleq ADD FUNCTION isfulleq_ARGTYPE(ARGTYPE, ARGTYPE); + +ALTER EXTENSION fulleq ADD FUNCTION fullhash_ARGTYPE(ARGTYPE); + +ALTER EXTENSION fulleq ADD OPERATOR == (ARGTYPE, ARGTYPE); + +ALTER EXTENSION fulleq ADD OPERATOR CLASS ARGTYPE_fill_ops USING hash; + diff --git a/contrib/fulleq/fulleq.c b/contrib/fulleq/fulleq.c new file mode 100644 index 00000000000..b6647282d43 --- /dev/null +++ b/contrib/fulleq/fulleq.c @@ -0,0 +1,113 @@ +#include "postgres.h" +#include "fmgr.h" +#include "access/hash.h" +#include "catalog/pg_collation.h" +#include "utils/builtins.h" +#include "utils/bytea.h" +#include "utils/int8.h" +#include "utils/timestamp.h" +#include "utils/date.h" + +#ifdef PG_MODULE_MAGIC +PG_MODULE_MAGIC; +#endif + +#define NULLHASHVALUE (-2147483647) + +#define FULLEQ_FUNC(type, cmpfunc, hashfunc) \ +PG_FUNCTION_INFO_V1( isfulleq_##type ); \ +Datum isfulleq_##type(PG_FUNCTION_ARGS); \ +Datum \ +isfulleq_##type(PG_FUNCTION_ARGS) { \ + if ( PG_ARGISNULL(0) && PG_ARGISNULL(1) ) \ + PG_RETURN_BOOL(true); \ + else if ( PG_ARGISNULL(0) || PG_ARGISNULL(1) ) \ + PG_RETURN_BOOL(false); \ + \ + PG_RETURN_DATUM( DirectFunctionCall2Coll( cmpfunc, \ + DEFAULT_COLLATION_OID, \ + PG_GETARG_DATUM(0), \ + PG_GETARG_DATUM(1) \ + ) ); \ +} \ + \ +PG_FUNCTION_INFO_V1( fullhash_##type ); \ +Datum fullhash_##type(PG_FUNCTION_ARGS); \ +Datum \ +fullhash_##type(PG_FUNCTION_ARGS) { \ + if ( PG_ARGISNULL(0) ) \ + PG_RETURN_INT32(NULLHASHVALUE); \ + \ + PG_RETURN_DATUM( DirectFunctionCall1( hashfunc, \ + PG_GETARG_DATUM(0) \ + ) ); \ +} + + +static Datum +hashint2vector(PG_FUNCTION_ARGS) +{ + int2vector *key = (int2vector *) PG_GETARG_POINTER(0); + + return hash_any((unsigned char *) key->values, key->dim1 * sizeof(int16)); +} + +/* + * We don't have a complete set of int2vector support routines, + * but we need int2vectoreq for catcache indexing. + */ +static Datum +int2vectoreq(PG_FUNCTION_ARGS) +{ + int2vector *a = (int2vector *) PG_GETARG_POINTER(0); + int2vector *b = (int2vector *) PG_GETARG_POINTER(1); + + if (a->dim1 != b->dim1) + PG_RETURN_BOOL(false); + PG_RETURN_BOOL(memcmp(a->values, b->values, a->dim1 * sizeof(int16)) == 0); +} + + +FULLEQ_FUNC( bool , booleq , hashchar ); +FULLEQ_FUNC( bytea , byteaeq , hashvarlena ); +FULLEQ_FUNC( char , chareq , hashchar ); +FULLEQ_FUNC( name , nameeq , hashname ); +FULLEQ_FUNC( int8 , int8eq , hashint8 ); +FULLEQ_FUNC( int2 , int2eq , hashint2 ); +FULLEQ_FUNC( int4 , int4eq , hashint4 ); +FULLEQ_FUNC( text , texteq , hashtext ); +FULLEQ_FUNC( oid , oideq , hashoid ); +FULLEQ_FUNC( xid , xideq , hashint4 ); +FULLEQ_FUNC( cid , cideq , hashint4 ); +FULLEQ_FUNC( oidvector , oidvectoreq , hashoidvector ); +FULLEQ_FUNC( float4 , float4eq , hashfloat4 ); +FULLEQ_FUNC( float8 , float8eq , hashfloat8 ); +/*FULLEQ_FUNC( abstime , abstimeeq , hashint4 );*/ +/*FULLEQ_FUNC( reltime , reltimeeq , hashint4 );*/ +FULLEQ_FUNC( macaddr , macaddr_eq , hashmacaddr ); +FULLEQ_FUNC( inet , network_eq , hashinet ); +FULLEQ_FUNC( cidr , network_eq , hashinet ); +FULLEQ_FUNC( varchar , texteq , hashtext ); +FULLEQ_FUNC( date , date_eq , hashint4 ); +FULLEQ_FUNC( time , time_eq , hashfloat8 ); +FULLEQ_FUNC( timestamp , timestamp_eq , hashfloat8 ); +FULLEQ_FUNC( timestamptz , timestamp_eq , hashfloat8 ); +FULLEQ_FUNC( interval , interval_eq , interval_hash ); +FULLEQ_FUNC( timetz , timetz_eq , timetz_hash ); + +/* + * v10 drop * support for int2vector equality and hash operator in commit + * 5c80642aa8de8393b08cd3cbf612b325cedd98dc, but for compatibility + * we still add this operators + */ +FULLEQ_FUNC( int2vector , int2vectoreq , hashint2vector ); + +static Datum +dummy_eq(PG_FUNCTION_ARGS) +{ + elog(ERROR, "unimplemented"); + PG_RETURN_DATUM(0); //keep compiler quiet +} + +FULLEQ_FUNC( abstime , dummy_eq , hashint4 ); +FULLEQ_FUNC( reltime , dummy_eq , hashint4 ); diff --git a/contrib/fulleq/fulleq.control b/contrib/fulleq/fulleq.control new file mode 100644 index 00000000000..30a26c65fff --- /dev/null +++ b/contrib/fulleq/fulleq.control @@ -0,0 +1,5 @@ +comment = 'exact equal operation' +default_version = '2.0' +module_pathname = '$libdir/fulleq' +relocatable = true +trusted = true diff --git a/contrib/fulleq/fulleq.sql.in b/contrib/fulleq/fulleq.sql.in new file mode 100644 index 00000000000..c270647c720 --- /dev/null +++ b/contrib/fulleq/fulleq.sql.in @@ -0,0 +1,25 @@ +-- For ARGTYPE + +CREATE OR REPLACE FUNCTION isfulleq_ARGTYPE(ARGTYPE, ARGTYPE) +RETURNS bool AS 'MODULE_PATHNAME' +LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + +CREATE OR REPLACE FUNCTION fullhash_ARGTYPE(ARGTYPE) +RETURNS int4 AS 'MODULE_PATHNAME' +LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + + +CREATE OPERATOR == ( + LEFTARG = ARGTYPE, + RIGHTARG = ARGTYPE, + PROCEDURE = isfulleq_ARGTYPE, + COMMUTATOR = '==', + RESTRICT = eqsel, + JOIN = eqjoinsel, + HASHES +); + +CREATE OPERATOR CLASS ARGTYPE_fill_ops + FOR TYPE ARGTYPE USING hash AS + OPERATOR 1 ==, + FUNCTION 1 fullhash_ARGTYPE(ARGTYPE); diff --git a/contrib/fulleq/sql/fulleq.sql b/contrib/fulleq/sql/fulleq.sql new file mode 100644 index 00000000000..d43abeb34b7 --- /dev/null +++ b/contrib/fulleq/sql/fulleq.sql @@ -0,0 +1,13 @@ +CREATE EXTENSION fulleq; + +select 4::int == 4; +select 4::int == 5; +select 4::int == NULL; +select NULL::int == 5; +select NULL::int == NULL; + +select '4'::text == '4'; +select '4'::text == '5'; +select '4'::text == NULL; +select NULL::text == '5'; +select NULL::text == NULL; diff --git a/contrib/mchar/Changes b/contrib/mchar/Changes new file mode 100644 index 00000000000..b7f6e0c5718 --- /dev/null +++ b/contrib/mchar/Changes @@ -0,0 +1,20 @@ +2.0 make an extension +0.17 add == operation: + a == b => ( a = b or a is null and b is null ) +0.16 fix pg_dump - now mchar in pg_catalog scheme, not public + fix bug in mvarchar_substr() +0.15 add upper()/lower() +0.14 Add ESCAPE for LIKE, SIMILAR TO [ESCAPE], POSIX regexp +0.13 Outer binary format is now different from + inner: it's just a UTF-16 string +0.12 Fix copy binary +0.11 Force UTF-8 convertor if server_encoding='UTF8' +0.10 add (mchar|mvarchar)_(send|recv) functions to + allow binary copying. Note: that functions + don't recode values. +0.9 index support for like, improve recoding functions +0.8 initial suport for like optimizioation with index: + still thres no algo to find the nearest greater string +0.7 hash indexes and enable a hash joins +0.6 implicit casting mchar-mvarchar + cross type comparison operations diff --git a/contrib/mchar/Makefile b/contrib/mchar/Makefile new file mode 100644 index 00000000000..81826afd296 --- /dev/null +++ b/contrib/mchar/Makefile @@ -0,0 +1,31 @@ +MODULE_big = mchar +OBJS = mchar_io.o mchar_proc.o mchar_op.o mchar_recode.o mchar_like.o +EXTENSION=mchar +DATA = mchar--2.2.1.sql mchar--2.0.1--2.1.sql mchar--2.0--2.1.sql \ + mchar--2.1.1--2.2.sql mchar--2.1--2.2.sql \ + mchar--2.2--2.2.1.sql \ + mchar--unpackaged--2.0.sql +DOCS = README.mchar +REGRESS = init mchar mvarchar mm like compat +ENCODING = UTF8 + +PG_CPPFLAGS=-I/usr/local/include + +ifdef USE_PGXS +PGXS := $(shell pg_config --pgxs) +include $(PGXS) +else +subdir = contrib/mchar +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif + +ifeq ($(PORTNAME),win32) +ICUNAME=icuin +else +ICUNAME=icui18n +endif + +SHLIB_LINK += -L/usr/local/lib -licuuc -l$(ICUNAME) -Wl,-rpath,'$$ORIGIN' + diff --git a/contrib/mchar/README.mchar b/contrib/mchar/README.mchar new file mode 100644 index 00000000000..479a7d1f40a --- /dev/null +++ b/contrib/mchar/README.mchar @@ -0,0 +1,20 @@ +MCHAR & VARCHAR + type modifier + length() + substr(str, pos[, length]) + || - concatenation with any (mchar,mvarchar) arguments + < <= = >= > - case-insensitive comparisons (libICU) + &< &<= &= &>= &> - case-sensitive comparisons (libICU) + implicit casting mchar<->mvarchar + B-tree and hash index + LIKE [ESCAPE] + SIMILAR TO [ESCAPE] + ~ (POSIX regexp) + index support for LIKE + + +Authors: + Oleg Bartunov <oleg@sai.msu.ru> + Teodor Sigaev <teodor@sigaev.ru> + + diff --git a/contrib/mchar/expected/compat.out b/contrib/mchar/expected/compat.out new file mode 100644 index 00000000000..480a286e8f6 --- /dev/null +++ b/contrib/mchar/expected/compat.out @@ -0,0 +1,66 @@ +--- table based checks +select '<' || ch || '>', '<' || vch || '>' from chvch; + ?column? | ?column? +----------------+-------------- + <No spaces > | <No spaces> + <One space > | <One space > + <1 space > | <1 space > +(3 rows) + +select * from chvch where vch = 'One space'; + ch | vch +--------------+------------ + One space | One space +(1 row) + +select * from chvch where vch = 'One space '; + ch | vch +--------------+------------ + One space | One space +(1 row) + +select * from ch where chcol = 'abcd' order by chcol; + chcol +---------------------------------- + abcd + AbcD +(2 rows) + +select * from ch t1 join ch t2 on t1.chcol = t2.chcol order by t1.chcol, t2.chcol; + chcol | chcol +----------------------------------+---------------------------------- + abcd | AbcD + abcd | abcd + AbcD | AbcD + AbcD | abcd + abcz | abcz + defg | dEfg + defg | defg + dEfg | dEfg + dEfg | defg + ee | Ee + ee | ee + Ee | Ee + Ee | ee +(13 rows) + +select * from ch where chcol > 'abcd' and chcol<'ee'; + chcol +---------------------------------- + abcz + defg + dEfg +(3 rows) + +select * from ch order by chcol; + chcol +---------------------------------- + abcd + AbcD + abcz + defg + dEfg + ee + Ee +(7 rows) + diff --git a/contrib/mchar/expected/init.out b/contrib/mchar/expected/init.out new file mode 100644 index 00000000000..7bae978ec35 --- /dev/null +++ b/contrib/mchar/expected/init.out @@ -0,0 +1,18 @@ +CREATE EXTENSION mchar; +create table ch ( + chcol mchar(32) +) without oids; +insert into ch values('abcd'); +insert into ch values('AbcD'); +insert into ch values('abcz'); +insert into ch values('defg'); +insert into ch values('dEfg'); +insert into ch values('ee'); +insert into ch values('Ee'); +create table chvch ( + ch mchar(12), + vch mvarchar(12) +) without oids; +insert into chvch values('No spaces', 'No spaces'); +insert into chvch values('One space ', 'One space '); +insert into chvch values('1 space', '1 space '); diff --git a/contrib/mchar/expected/like.out b/contrib/mchar/expected/like.out new file mode 100644 index 00000000000..a3f47f8c710 --- /dev/null +++ b/contrib/mchar/expected/like.out @@ -0,0 +1,841 @@ +-- simplest examples +-- E061-04 like predicate +set standard_conforming_strings=off; +SELECT 'hawkeye'::mchar LIKE 'h%' AS "true"; + true +------ + t +(1 row) + +SELECT 'hawkeye'::mchar NOT LIKE 'h%' AS "false"; + false +------- + f +(1 row) + +SELECT 'hawkeye'::mchar LIKE 'H%' AS "true"; + true +------ + t +(1 row) + +SELECT 'hawkeye'::mchar NOT LIKE 'H%' AS "false"; + false +------- + f +(1 row) + +SELECT 'hawkeye'::mchar LIKE 'indio%' AS "false"; + false +------- + f +(1 row) + +SELECT 'hawkeye'::mchar NOT LIKE 'indio%' AS "true"; + true +------ + t +(1 row) + +SELECT 'hawkeye'::mchar LIKE 'h%eye' AS "true"; + true +------ + t +(1 row) + +SELECT 'hawkeye'::mchar NOT LIKE 'h%eye' AS "false"; + false +------- + f +(1 row) + +SELECT 'indio'::mchar LIKE '_ndio' AS "true"; + true +------ + t +(1 row) + +SELECT 'indio'::mchar NOT LIKE '_ndio' AS "false"; + false +------- + f +(1 row) + +SELECT 'indio'::mchar LIKE 'in__o' AS "true"; + true +------ + t +(1 row) + +SELECT 'indio'::mchar NOT LIKE 'in__o' AS "false"; + false +------- + f +(1 row) + +SELECT 'indio'::mchar LIKE 'in_o' AS "false"; + false +------- + f +(1 row) + +SELECT 'indio'::mchar NOT LIKE 'in_o' AS "true"; + true +------ + t +(1 row) + +SELECT 'hawkeye'::mvarchar LIKE 'h%' AS "true"; + true +------ + t +(1 row) + +SELECT 'hawkeye'::mvarchar NOT LIKE 'h%' AS "false"; + false +------- + f +(1 row) + +SELECT 'hawkeye'::mvarchar LIKE 'H%' AS "true"; + true +------ + t +(1 row) + +SELECT 'hawkeye'::mvarchar NOT LIKE 'H%' AS "false"; + false +------- + f +(1 row) + +SELECT 'hawkeye'::mvarchar LIKE 'indio%' AS "false"; + false +------- + f +(1 row) + +SELECT 'hawkeye'::mvarchar NOT LIKE 'indio%' AS "true"; + true +------ + t +(1 row) + +SELECT 'hawkeye'::mvarchar LIKE 'h%eye' AS "true"; + true +------ + t +(1 row) + +SELECT 'hawkeye'::mvarchar NOT LIKE 'h%eye' AS "false"; + false +------- + f +(1 row) + +SELECT 'indio'::mvarchar LIKE '_ndio' AS "true"; + true +------ + t +(1 row) + +SELECT 'indio'::mvarchar NOT LIKE '_ndio' AS "false"; + false +------- + f +(1 row) + +SELECT 'indio'::mvarchar LIKE 'in__o' AS "true"; + true +------ + t +(1 row) + +SELECT 'indio'::mvarchar NOT LIKE 'in__o' AS "false"; + false +------- + f +(1 row) + +SELECT 'indio'::mvarchar LIKE 'in_o' AS "false"; + false +------- + f +(1 row) + +SELECT 'indio'::mvarchar NOT LIKE 'in_o' AS "true"; + true +------ + t +(1 row) + +-- unused escape character +SELECT 'hawkeye'::mchar LIKE 'h%'::mchar ESCAPE '#' AS "true"; + true +------ + t +(1 row) + +SELECT 'hawkeye'::mchar NOT LIKE 'h%'::mchar ESCAPE '#' AS "false"; + false +------- + f +(1 row) + +SELECT 'indio'::mchar LIKE 'ind_o'::mchar ESCAPE '$' AS "true"; + true +------ + t +(1 row) + +SELECT 'indio'::mchar NOT LIKE 'ind_o'::mchar ESCAPE '$' AS "false"; + false +------- + f +(1 row) + +-- escape character +-- E061-05 like predicate with escape clause +SELECT 'h%'::mchar LIKE 'h#%'::mchar ESCAPE '#' AS "true"; + true +------ + t +(1 row) + +SELECT 'h%'::mchar NOT LIKE 'h#%'::mchar ESCAPE '#' AS "false"; + false +------- + f +(1 row) + +SELECT 'h%wkeye'::mchar LIKE 'h#%'::mchar ESCAPE '#' AS "false"; + false +------- + f +(1 row) + +SELECT 'h%wkeye'::mchar NOT LIKE 'h#%'::mchar ESCAPE '#' AS "true"; + true +------ + t +(1 row) + +SELECT 'h%wkeye'::mchar LIKE 'h#%%'::mchar ESCAPE '#' AS "true"; + true +------ + t +(1 row) + +SELECT 'h%wkeye'::mchar NOT LIKE 'h#%%'::mchar ESCAPE '#' AS "false"; + false +------- + f +(1 row) + +SELECT 'h%awkeye'::mchar LIKE 'h#%a%k%e'::mchar ESCAPE '#' AS "true"; + true +------ + t +(1 row) + +SELECT 'h%awkeye'::mchar NOT LIKE 'h#%a%k%e'::mchar ESCAPE '#' AS "false"; + false +------- + f +(1 row) + +SELECT 'indio'::mchar LIKE '_ndio'::mchar ESCAPE '$' AS "true"; + true +------ + t +(1 row) + +SELECT 'indio'::mchar NOT LIKE '_ndio'::mchar ESCAPE '$' AS "false"; + false +------- + f +(1 row) + +SELECT 'i_dio'::mchar LIKE 'i$_d_o'::mchar ESCAPE '$' AS "true"; + true +------ + t +(1 row) + +SELECT 'i_dio'::mchar NOT LIKE 'i$_d_o'::mchar ESCAPE '$' AS "false"; + false +------- + f +(1 row) + +SELECT 'i_dio'::mchar LIKE 'i$_nd_o'::mchar ESCAPE '$' AS "false"; + false +------- + f +(1 row) + +SELECT 'i_dio'::mchar NOT LIKE 'i$_nd_o'::mchar ESCAPE '$' AS "true"; + true +------ + t +(1 row) + +SELECT 'i_dio'::mchar LIKE 'i$_d%o'::mchar ESCAPE '$' AS "true"; + true +------ + t +(1 row) + +SELECT 'i_dio'::mchar NOT LIKE 'i$_d%o'::mchar ESCAPE '$' AS "false"; + false +------- + f +(1 row) + +-- escape character same as pattern character +SELECT 'maca'::mchar LIKE 'm%aca' ESCAPE '%'::mchar AS "true"; + true +------ + t +(1 row) + +SELECT 'maca'::mchar NOT LIKE 'm%aca' ESCAPE '%'::mchar AS "false"; + false +------- + f +(1 row) + +SELECT 'ma%a'::mchar LIKE 'm%a%%a' ESCAPE '%'::mchar AS "true"; + true +------ + t +(1 row) + +SELECT 'ma%a'::mchar NOT LIKE 'm%a%%a' ESCAPE '%'::mchar AS "false"; + false +------- + f +(1 row) + +SELECT 'bear'::mchar LIKE 'b_ear' ESCAPE '_'::mchar AS "true"; + true +------ + t +(1 row) + +SELECT 'bear'::mchar NOT LIKE 'b_ear'::mchar ESCAPE '_' AS "false"; + false +------- + f +(1 row) + +SELECT 'be_r'::mchar LIKE 'b_e__r' ESCAPE '_'::mchar AS "true"; + true +------ + t +(1 row) + +SELECT 'be_r'::mchar NOT LIKE 'b_e__r' ESCAPE '_'::mchar AS "false"; + false +------- + f +(1 row) + +SELECT 'be_r'::mchar LIKE '__e__r' ESCAPE '_'::mchar AS "false"; + false +------- + f +(1 row) + +SELECT 'be_r'::mchar NOT LIKE '__e__r'::mchar ESCAPE '_' AS "true"; + true +------ + t +(1 row) + +-- unused escape character +SELECT 'hawkeye'::mvarchar LIKE 'h%'::mvarchar ESCAPE '#' AS "true"; + true +------ + t +(1 row) + +SELECT 'hawkeye'::mvarchar NOT LIKE 'h%'::mvarchar ESCAPE '#' AS "false"; + false +------- + f +(1 row) + +SELECT 'indio'::mvarchar LIKE 'ind_o'::mvarchar ESCAPE '$' AS "true"; + true +------ + t +(1 row) + +SELECT 'indio'::mvarchar NOT LIKE 'ind_o'::mvarchar ESCAPE '$' AS "false"; + false +------- + f +(1 row) + +-- escape character +-- E061-05 like predicate with escape clause +SELECT 'h%'::mvarchar LIKE 'h#%'::mvarchar ESCAPE '#' AS "true"; + true +------ + t +(1 row) + +SELECT 'h%'::mvarchar NOT LIKE 'h#%'::mvarchar ESCAPE '#' AS "false"; + false +------- + f +(1 row) + +SELECT 'h%wkeye'::mvarchar LIKE 'h#%'::mvarchar ESCAPE '#' AS "false"; + false +------- + f +(1 row) + +SELECT 'h%wkeye'::mvarchar NOT LIKE 'h#%'::mvarchar ESCAPE '#' AS "true"; + true +------ + t +(1 row) + +SELECT 'h%wkeye'::mvarchar LIKE 'h#%%'::mvarchar ESCAPE '#' AS "true"; + true +------ + t +(1 row) + +SELECT 'h%wkeye'::mvarchar NOT LIKE 'h#%%'::mvarchar ESCAPE '#' AS "false"; + false +------- + f +(1 row) + +SELECT 'h%awkeye'::mvarchar LIKE 'h#%a%k%e'::mvarchar ESCAPE '#' AS "true"; + true +------ + t +(1 row) + +SELECT 'h%awkeye'::mvarchar NOT LIKE 'h#%a%k%e'::mvarchar ESCAPE '#' AS "false"; + false +------- + f +(1 row) + +SELECT 'indio'::mvarchar LIKE '_ndio'::mvarchar ESCAPE '$' AS "true"; + true +------ + t +(1 row) + +SELECT 'indio'::mvarchar NOT LIKE '_ndio'::mvarchar ESCAPE '$' AS "false"; + false +------- + f +(1 row) + +SELECT 'i_dio'::mvarchar LIKE 'i$_d_o'::mvarchar ESCAPE '$' AS "true"; + true +------ + t +(1 row) + +SELECT 'i_dio'::mvarchar NOT LIKE 'i$_d_o'::mvarchar ESCAPE '$' AS "false"; + false +------- + f +(1 row) + +SELECT 'i_dio'::mvarchar LIKE 'i$_nd_o'::mvarchar ESCAPE '$' AS "false"; + false +------- + f +(1 row) + +SELECT 'i_dio'::mvarchar NOT LIKE 'i$_nd_o'::mvarchar ESCAPE '$' AS "true"; + true +------ + t +(1 row) + +SELECT 'i_dio'::mvarchar LIKE 'i$_d%o'::mvarchar ESCAPE '$' AS "true"; + true +------ + t +(1 row) + +SELECT 'i_dio'::mvarchar NOT LIKE 'i$_d%o'::mvarchar ESCAPE '$' AS "false"; + false +------- + f +(1 row) + +-- escape character same as pattern character +SELECT 'maca'::mvarchar LIKE 'm%aca' ESCAPE '%'::mvarchar AS "true"; + true +------ + t +(1 row) + +SELECT 'maca'::mvarchar NOT LIKE 'm%aca' ESCAPE '%'::mvarchar AS "false"; + false +------- + f +(1 row) + +SELECT 'ma%a'::mvarchar LIKE 'm%a%%a' ESCAPE '%'::mvarchar AS "true"; + true +------ + t +(1 row) + +SELECT 'ma%a'::mvarchar NOT LIKE 'm%a%%a' ESCAPE '%'::mvarchar AS "false"; + false +------- + f +(1 row) + +SELECT 'bear'::mvarchar LIKE 'b_ear' ESCAPE '_'::mvarchar AS "true"; + true +------ + t +(1 row) + +SELECT 'bear'::mvarchar NOT LIKE 'b_ear'::mvarchar ESCAPE '_' AS "false"; + false +------- + f +(1 row) + +SELECT 'be_r'::mvarchar LIKE 'b_e__r' ESCAPE '_'::mvarchar AS "true"; + true +------ + t +(1 row) + +SELECT 'be_r'::mvarchar NOT LIKE 'b_e__r' ESCAPE '_'::mvarchar AS "false"; + false +------- + f +(1 row) + +SELECT 'be_r'::mvarchar LIKE '__e__r' ESCAPE '_'::mvarchar AS "false"; + false +------- + f +(1 row) + +SELECT 'be_r'::mvarchar NOT LIKE '__e__r'::mvarchar ESCAPE '_' AS "true"; + true +------ + t +(1 row) + +-- similar to +SELECT 'abc'::mchar SIMILAR TO 'abc'::mchar AS "true"; + true +------ + t +(1 row) + +SELECT 'abc'::mchar SIMILAR TO 'a'::mchar AS "false"; + false +------- + f +(1 row) + +SELECT 'abc'::mchar SIMILAR TO '%(b|d)%'::mchar AS "true"; + true +------ + t +(1 row) + +SELECT 'abc'::mchar SIMILAR TO '(b|c)%'::mchar AS "false"; + false +------- + f +(1 row) + +SELECT 'h%'::mchar SIMILAR TO 'h#%'::mchar AS "false"; + false +------- + f +(1 row) + +SELECT 'h%'::mchar SIMILAR TO 'h#%'::mchar ESCAPE '#' AS "true"; + true +------ + t +(1 row) + +SELECT 'abc'::mvarchar SIMILAR TO 'abc'::mvarchar AS "true"; + true +------ + t +(1 row) + +SELECT 'abc'::mvarchar SIMILAR TO 'a'::mvarchar AS "false"; + false +------- + f +(1 row) + +SELECT 'abc'::mvarchar SIMILAR TO '%(b|d)%'::mvarchar AS "true"; + true +------ + t +(1 row) + +SELECT 'abc'::mvarchar SIMILAR TO '(b|c)%'::mvarchar AS "false"; + false +------- + f +(1 row) + +SELECT 'h%'::mvarchar SIMILAR TO 'h#%'::mvarchar AS "false"; + false +------- + f +(1 row) + +SELECT 'h%'::mvarchar SIMILAR TO 'h#%'::mvarchar ESCAPE '#' AS "true"; + true +------ + t +(1 row) + +-- index support +SELECT * from ch where chcol like 'aB_d' order by chcol using &<; + chcol +---------------------------------- + AbcD + abcd +(2 rows) + +SELECT * from ch where chcol like 'aB%d' order by chcol using &<; + chcol +---------------------------------- + AbcD + abcd +(2 rows) + +SELECT * from ch where chcol like 'aB%' order by chcol using &<; + chcol +---------------------------------- + AbcD + abcd + abcz +(3 rows) + +SELECT * from ch where chcol like '%BC%' order by chcol using &<; + chcol +---------------------------------- + AbcD + abcd + abcz +(3 rows) + +set enable_seqscan = off; +explain (costs off) +SELECT * from ch where chcol like 'aB_d' order by chcol using &<; + QUERY PLAN +------------------------------------------------------------------------------ + Sort + Sort Key: chcol USING &< + -> Index Only Scan using qq on ch + Index Cond: ((chcol >= 'aB'::mvarchar) AND (chcol < 'aC'::mvarchar)) + Filter: (chcol ~~ 'aB_d'::mvarchar) +(5 rows) + +SELECT * from ch where chcol like 'aB_d' order by chcol using &<; + chcol +---------------------------------- + AbcD + abcd +(2 rows) + +SELECT * from ch where chcol like 'aB%d' order by chcol using &<; + chcol +---------------------------------- + AbcD + abcd +(2 rows) + +SELECT * from ch where chcol like 'aB%' order by chcol using &<; + chcol +---------------------------------- + AbcD + abcd + abcz +(3 rows) + +SELECT * from ch where chcol like '%BC%' order by chcol using &<; + chcol +---------------------------------- + AbcD + abcd + abcz +(3 rows) + +set enable_seqscan = on; +create table testt (f1 mchar(10)); +insert into testt values ('Abc-000001'); +insert into testt values ('Abc-000002'); +insert into testt values ('0000000001'); +insert into testt values ('0000000002'); +select f1 from testt where f1::mvarchar like E'Abc\\-%'::mvarchar; + f1 +------------ + Abc-000001 + Abc-000002 +(2 rows) + +select * from testt where f1::mchar like E'Abc\\-%'::mchar; + f1 +------------ + Abc-000001 + Abc-000002 +(2 rows) + +create index testindex on testt(f1); +set enable_seqscan=off; +explain (costs off) +select f1 from testt where f1::mvarchar like E'Abc\\-%'::mvarchar; + QUERY PLAN +--------------------------------------------------- + Index Only Scan using testindex on testt + Filter: ((f1)::mvarchar ~~ 'Abc\\-%'::mvarchar) +(2 rows) + +select f1 from testt where f1::mvarchar like E'Abc\\-%'::mvarchar; + f1 +------------ + Abc-000001 + Abc-000002 +(2 rows) + +select * from testt where f1::mchar like E'Abc\\-%'::mchar; + f1 +------------ + Abc-000001 + Abc-000002 +(2 rows) + +set enable_seqscan = on; +drop table testt; +create table testt (f1 mvarchar(10)); +insert into testt values ('Abc-000001'); +insert into testt values ('Abc-000002'); +insert into testt values ('0000000001'); +insert into testt values ('0000000002'); +select f1 from testt where f1::mvarchar like E'Abc\\-%'::mvarchar; + f1 +------------ + Abc-000001 + Abc-000002 +(2 rows) + +select * from testt where f1::mchar like E'Abc\\-%'::mchar; + f1 +------------ + Abc-000001 + Abc-000002 +(2 rows) + +select * from testt where f1::mchar like E'Abc\\- %'::mchar; + f1 +------------ + Abc-000001 + Abc-000002 +(2 rows) + +select * from testt where f1::mchar like E' %'::mchar; + f1 +------------ + Abc-000001 + Abc-000002 + 0000000001 + 0000000002 +(4 rows) + +create index testindex on testt(f1); +set enable_seqscan=off; +explain (costs off) +select f1 from testt where f1::mvarchar like E'Abc\\-%'::mvarchar; + QUERY PLAN +---------------------------------------------------------------------- + Index Only Scan using testindex on testt + Index Cond: ((f1 >= 'Abc-'::mvarchar) AND (f1 < 'Abc.'::mvarchar)) + Filter: ((f1)::mvarchar ~~ 'Abc\\-%'::mvarchar) +(3 rows) + +select f1 from testt where f1::mvarchar like E'Abc\\-%'::mvarchar; + f1 +------------ + Abc-000001 + Abc-000002 +(2 rows) + +select * from testt where f1::mchar like E'Abc\\-%'::mchar; + f1 +------------ + Abc-000001 + Abc-000002 +(2 rows) + +select * from testt where f1::mchar like E'Abc\\- %'::mchar; + f1 +------------ + Abc-000001 + Abc-000002 +(2 rows) + +select * from testt where f1::mchar like E' %'::mchar; + f1 +------------ + 0000000001 + 0000000002 + Abc-000001 + Abc-000002 +(4 rows) + +set enable_seqscan = on; +drop table testt; +CREATE TABLE test ( code mchar(5) NOT NULL ); +insert into test values('1111 '); +insert into test values('111 '); +insert into test values('11 '); +insert into test values('1 '); +SELECT * FROM test WHERE code LIKE ('% '); + code +------- + 1 +(1 row) + +set escape_string_warning = off; +SELECT CASE WHEN ('_'::text SIMILAR TO '[\\_]'::text ESCAPE '\\'::text) THEN TRUE ELSE FALSE END ; + case +------ + t +(1 row) + +SELECT CASE WHEN ('_'::mchar SIMILAR TO '[\\_]'::mchar ESCAPE '\\'::mchar) THEN TRUE ELSE FALSE END ; + case +------ + t +(1 row) + +SELECT CASE WHEN ('_'::mvarchar SIMILAR TO '[\\_]'::mvarchar ESCAPE '\\'::mvarchar) THEN TRUE ELSE FALSE END ; + case +------ + t +(1 row) + +reset escape_string_warning; +reset standard_conforming_strings; diff --git a/contrib/mchar/expected/mchar.out b/contrib/mchar/expected/mchar.out new file mode 100644 index 00000000000..f6c592fd16f --- /dev/null +++ b/contrib/mchar/expected/mchar.out @@ -0,0 +1,382 @@ +-- I/O tests +select '1'::mchar; + mchar +------- + 1 +(1 row) + +select '2 '::mchar; + mchar +------- + 2 +(1 row) + +select '10 '::mchar; + mchar +------- + 10 +(1 row) + +select '1'::mchar(2); + mchar +------- + 1 +(1 row) + +select '2 '::mchar(2); + mchar +------- + 2 +(1 row) + +select '3 '::mchar(2); + mchar +------- + 3 +(1 row) + +select '10 '::mchar(2); + mchar +------- + 10 +(1 row) + +select ' '::mchar(10); + mchar +------------ + +(1 row) + +select ' '::mchar; + mchar +------- + +(1 row) + +-- operations & functions +select length('1'::mchar); + length +-------- + 1 +(1 row) + +select length('2 '::mchar); + length +-------- + 1 +(1 row) + +select length('10 '::mchar); + length +-------- + 2 +(1 row) + +select length('1'::mchar(2)); + length +-------- + 1 +(1 row) + +select length('2 '::mchar(2)); + length +-------- + 1 +(1 row) + +select length('3 '::mchar(2)); + length +-------- + 1 +(1 row) + +select length('10 '::mchar(2)); + length +-------- + 2 +(1 row) + +select length(' '::mchar(10)); + length +-------- + 0 +(1 row) + +select length(' '::mchar); + length +-------- + 0 +(1 row) + +select 'asd'::mchar(10) || '>'::mchar(10); + ?column? +---------------------- + asd > +(1 row) + +select length('asd'::mchar(10) || '>'::mchar(10)); + length +-------- + 11 +(1 row) + +select 'asd'::mchar(2) || '>'::mchar(10); + ?column? +-------------- + as> +(1 row) + +select length('asd'::mchar(2) || '>'::mchar(10)); + length +-------- + 3 +(1 row) + +-- Comparisons +select 'asdf'::mchar = 'aSdf'::mchar; + ?column? +---------- + t +(1 row) + +select 'asdf'::mchar = 'aSdf '::mchar; + ?column? +---------- + t +(1 row) + +select 'asdf'::mchar = 'aSdf 1'::mchar(4); + ?column? +---------- + t +(1 row) + +select 'asdf'::mchar = 'aSdf 1'::mchar(5); + ?column? +---------- + t +(1 row) + +select 'asdf'::mchar = 'aSdf 1'::mchar(6); + ?column? +---------- + f +(1 row) + +select 'asdf'::mchar(3) = 'aSdf 1'::mchar(5); + ?column? +---------- + f +(1 row) + +select 'asdf'::mchar(3) = 'aSdf 1'::mchar(3); + ?column? +---------- + t +(1 row) + +select 'asdf'::mchar < 'aSdf'::mchar; + ?column? +---------- + f +(1 row) + +select 'asdf'::mchar < 'aSdf '::mchar; + ?column? +---------- + f +(1 row) + +select 'asdf'::mchar < 'aSdf 1'::mchar(4); + ?column? +---------- + f +(1 row) + +select 'asdf'::mchar < 'aSdf 1'::mchar(5); + ?column? +---------- + f +(1 row) + +select 'asdf'::mchar < 'aSdf 1'::mchar(6); + ?column? +---------- + t +(1 row) + +select 'asdf'::mchar <= 'aSdf'::mchar; + ?column? +---------- + t +(1 row) + +select 'asdf'::mchar <= 'aSdf '::mchar; + ?column? +---------- + t +(1 row) + +select 'asdf'::mchar <= 'aSdf 1'::mchar(4); + ?column? +---------- + t +(1 row) + +select 'asdf'::mchar <= 'aSdf 1'::mchar(5); + ?column? +---------- + t +(1 row) + +select 'asdf'::mchar <= 'aSdf 1'::mchar(6); + ?column? +---------- + t +(1 row) + +select 'asdf'::mchar >= 'aSdf'::mchar; + ?column? +---------- + t +(1 row) + +select 'asdf'::mchar >= 'aSdf '::mchar; + ?column? +---------- + t +(1 row) + +select 'asdf'::mchar >= 'aSdf 1'::mchar(4); + ?column? +---------- + t +(1 row) + +select 'asdf'::mchar >= 'aSdf 1'::mchar(5); + ?column? +---------- + t +(1 row) + +select 'asdf'::mchar >= 'aSdf 1'::mchar(6); + ?column? +---------- + f +(1 row) + +select 'asdf'::mchar > 'aSdf'::mchar; + ?column? +---------- + f +(1 row) + +select 'asdf'::mchar > 'aSdf '::mchar; + ?column? +---------- + f +(1 row) + +select 'asdf'::mchar > 'aSdf 1'::mchar(4); + ?column? +---------- + f +(1 row) + +select 'asdf'::mchar > 'aSdf 1'::mchar(5); + ?column? +---------- + f +(1 row) + +select 'asdf'::mchar > 'aSdf 1'::mchar(6); + ?column? +---------- + f +(1 row) + +select max(ch) from chvch; + max +-------------- + One space +(1 row) + +select min(ch) from chvch; + min +-------------- + 1 space +(1 row) + +select substr('1234567890'::mchar, 3) = '34567890' as "34567890"; + 34567890 +---------- + f +(1 row) + +select substr('1234567890'::mchar, 4, 3) = '456' as "456"; + 456 +----- + t +(1 row) + +select lower('asdfASDF'::mchar); + lower +---------- + asdfasdf +(1 row) + +select upper('asdfASDF'::mchar); + upper +---------- + ASDFASDF +(1 row) + +select 'asd'::mchar == 'aSd'::mchar; + ?column? +---------- + t +(1 row) + +select 'asd'::mchar == 'aCd'::mchar; + ?column? +---------- + f +(1 row) + +select 'asd'::mchar == NULL; + ?column? +---------- + f +(1 row) + +select NULL == 'aCd'::mchar; + ?column? +---------- + f +(1 row) + +select NULL::mchar == NULL; + ?column? +---------- + t +(1 row) + +--Note: here we use different space symbols, be carefull to copy it! +select v, count(*) from +(values (1, '4 242'::mchar), (2, '4 242'), (3, 'aSDF'), (4, 'asdf')) as t(i,v) group by v; + v | count +-------+------- + aSDF | 2 + 4 242 | 2 +(2 rows) + +set enable_hashagg=off; +select v, count(*) from +(values (1, '4 242'::mchar), (2, '4 242'), (3, 'aSDF'), (4, 'asdf')) as t(i,v) group by v; + v | count +-------+------- + 4 242 | 2 + aSDF | 2 +(2 rows) + +reset enable_hashagg; diff --git a/contrib/mchar/expected/mm.out b/contrib/mchar/expected/mm.out new file mode 100644 index 00000000000..c5b36c21611 --- /dev/null +++ b/contrib/mchar/expected/mm.out @@ -0,0 +1,855 @@ +select 'asd'::mchar::mvarchar; + mvarchar +---------- + asd +(1 row) + +select 'asd '::mchar::mvarchar; + mvarchar +---------- + asd +(1 row) + +select 'asd'::mchar(2)::mvarchar; + mvarchar +---------- + as +(1 row) + +select 'asd '::mchar(2)::mvarchar; + mvarchar +---------- + as +(1 row) + +select 'asd'::mchar(5)::mvarchar; + mvarchar +---------- + asd +(1 row) + +select 'asd '::mchar(5)::mvarchar; + mvarchar +---------- + asd +(1 row) + +select 'asd'::mchar::mvarchar(2); + mvarchar +---------- + as +(1 row) + +select 'asd '::mchar::mvarchar(2); + mvarchar +---------- + as +(1 row) + +select 'asd'::mchar(2)::mvarchar(2); + mvarchar +---------- + as +(1 row) + +select 'asd '::mchar(2)::mvarchar(2); + mvarchar +---------- + as +(1 row) + +select 'asd'::mchar(5)::mvarchar(2); + mvarchar +---------- + as +(1 row) + +select 'asd '::mchar(5)::mvarchar(2); + mvarchar +---------- + as +(1 row) + +select 'asd'::mchar::mvarchar(5); + mvarchar +---------- + asd +(1 row) + +select 'asd '::mchar::mvarchar(5); + mvarchar +---------- + asd +(1 row) + +select 'asd'::mchar(2)::mvarchar(5); + mvarchar +---------- + as +(1 row) + +select 'asd '::mchar(2)::mvarchar(5); + mvarchar +---------- + as +(1 row) + +select 'asd'::mchar(5)::mvarchar(5); + mvarchar +---------- + asd +(1 row) + +select 'asd '::mchar(5)::mvarchar(5); + mvarchar +---------- + asd +(1 row) + +select 'asd'::mvarchar::mchar; + mchar +------- + asd +(1 row) + +select 'asd '::mvarchar::mchar; + mchar +------- + asd +(1 row) + +select 'asd'::mvarchar(2)::mchar; + mchar +------- + as +(1 row) + +select 'asd '::mvarchar(2)::mchar; + mchar +------- + as +(1 row) + +select 'asd'::mvarchar(5)::mchar; + mchar +------- + asd +(1 row) + +select 'asd '::mvarchar(5)::mchar; + mchar +------- + asd +(1 row) + +select 'asd'::mvarchar::mchar(2); + mchar +------- + as +(1 row) + +select 'asd '::mvarchar::mchar(2); + mchar +------- + as +(1 row) + +select 'asd'::mvarchar(2)::mchar(2); + mchar +------- + as +(1 row) + +select 'asd '::mvarchar(2)::mchar(2); + mchar +------- + as +(1 row) + +select 'asd'::mvarchar(5)::mchar(2); + mchar +------- + as +(1 row) + +select 'asd '::mvarchar(5)::mchar(2); + mchar +------- + as +(1 row) + +select 'asd'::mvarchar::mchar(5); + mchar +------- + asd +(1 row) + +select 'asd '::mvarchar::mchar(5); + mchar +------- + asd +(1 row) + +select 'asd'::mvarchar(2)::mchar(5); + mchar +------- + as +(1 row) + +select 'asd '::mvarchar(2)::mchar(5); + mchar +------- + as +(1 row) + +select 'asd'::mvarchar(5)::mchar(5); + mchar +------- + asd +(1 row) + +select 'asd '::mvarchar(5)::mchar(5); + mchar +------- + asd +(1 row) + +select 'asd'::mchar || '123'; + ?column? +---------- + asd123 +(1 row) + +select 'asd'::mchar || '123'::mchar; + ?column? +---------- + asd123 +(1 row) + +select 'asd'::mchar || '123'::mvarchar; + ?column? +---------- + asd123 +(1 row) + +select 'asd '::mchar || '123'; + ?column? +---------- + asd123 +(1 row) + +select 'asd '::mchar || '123'::mchar; + ?column? +---------- + asd123 +(1 row) + +select 'asd '::mchar || '123'::mvarchar; + ?column? +---------- + asd123 +(1 row) + +select 'asd '::mchar || '123 '; + ?column? +---------- + asd123 +(1 row) + +select 'asd '::mchar || '123 '::mchar; + ?column? +---------- + asd123 +(1 row) + +select 'asd '::mchar || '123 '::mvarchar; + ?column? +---------- + asd123 +(1 row) + +select 'asd'::mvarchar || '123'; + ?column? +---------- + asd123 +(1 row) + +select 'asd'::mvarchar || '123'::mchar; + ?column? +---------- + asd123 +(1 row) + +select 'asd'::mvarchar || '123'::mvarchar; + ?column? +---------- + asd123 +(1 row) + +select 'asd '::mvarchar || '123'; + ?column? +---------- + asd 123 +(1 row) + +select 'asd '::mvarchar || '123'::mchar; + ?column? +---------- + asd 123 +(1 row) + +select 'asd '::mvarchar || '123'::mvarchar; + ?column? +---------- + asd 123 +(1 row) + +select 'asd '::mvarchar || '123 '; + ?column? +---------- + asd 123 +(1 row) + +select 'asd '::mvarchar || '123 '::mchar; + ?column? +---------- + asd 123 +(1 row) + +select 'asd '::mvarchar || '123 '::mvarchar; + ?column? +---------- + asd 123 +(1 row) + +select 'asd'::mchar(2) || '123'; + ?column? +---------- + as123 +(1 row) + +select 'asd'::mchar(2) || '123'::mchar; + ?column? +---------- + as123 +(1 row) + +select 'asd'::mchar(2) || '123'::mvarchar; + ?column? +---------- + as123 +(1 row) + +select 'asd '::mchar(2) || '123'; + ?column? +---------- + as123 +(1 row) + +select 'asd '::mchar(2) || '123'::mchar; + ?column? +---------- + as123 +(1 row) + +select 'asd '::mchar(2) || '123'::mvarchar; + ?column? +---------- + as123 +(1 row) + +select 'asd '::mchar(2) || '123 '; + ?column? +---------- + as123 +(1 row) + +select 'asd '::mchar(2) || '123 '::mchar; + ?column? +---------- + as123 +(1 row) + +select 'asd '::mchar(2) || '123 '::mvarchar; + ?column? +---------- + as123 +(1 row) + +select 'asd'::mvarchar(2) || '123'; + ?column? +---------- + as123 +(1 row) + +select 'asd'::mvarchar(2) || '123'::mchar; + ?column? +---------- + as123 +(1 row) + +select 'asd'::mvarchar(2) || '123'::mvarchar; + ?column? +---------- + as123 +(1 row) + +select 'asd '::mvarchar(2) || '123'; + ?column? +---------- + as123 +(1 row) + +select 'asd '::mvarchar(2) || '123'::mchar; + ?column? +---------- + as123 +(1 row) + +select 'asd '::mvarchar(2) || '123'::mvarchar; + ?column? +---------- + as123 +(1 row) + +select 'asd '::mvarchar(2) || '123 '; + ?column? +---------- + as123 +(1 row) + +select 'asd '::mvarchar(2) || '123 '::mchar; + ?column? +---------- + as123 +(1 row) + +select 'asd '::mvarchar(2) || '123 '::mvarchar; + ?column? +---------- + as123 +(1 row) + +select 'asd'::mchar(4) || '143'; + ?column? +---------- + asd 143 +(1 row) + +select 'asd'::mchar(4) || '123'::mchar; + ?column? +---------- + asd 123 +(1 row) + +select 'asd'::mchar(4) || '123'::mvarchar; + ?column? +---------- + asd 123 +(1 row) + +select 'asd '::mchar(4) || '123'; + ?column? +---------- + asd 123 +(1 row) + +select 'asd '::mchar(4) || '123'::mchar; + ?column? +---------- + asd 123 +(1 row) + +select 'asd '::mchar(4) || '123'::mvarchar; + ?column? +---------- + asd 123 +(1 row) + +select 'asd '::mchar(4) || '123 '; + ?column? +---------- + asd 123 +(1 row) + +select 'asd '::mchar(4) || '123 '::mchar; + ?column? +---------- + asd 123 +(1 row) + +select 'asd '::mchar(4) || '123 '::mvarchar; + ?column? +---------- + asd 123 +(1 row) + +select 'asd'::mvarchar(4) || '123'; + ?column? +---------- + asd123 +(1 row) + +select 'asd'::mvarchar(4) || '123'::mchar; + ?column? +---------- + asd123 +(1 row) + +select 'asd'::mvarchar(4) || '123'::mvarchar; + ?column? +---------- + asd123 +(1 row) + +select 'asd '::mvarchar(4) || '123'; + ?column? +---------- + asd 123 +(1 row) + +select 'asd '::mvarchar(4) || '123'::mchar; + ?column? +---------- + asd 123 +(1 row) + +select 'asd '::mvarchar(4) || '123'::mvarchar; + ?column? +---------- + asd 123 +(1 row) + +select 'asd '::mvarchar(4) || '123 '; + ?column? +---------- + asd 123 +(1 row) + +select 'asd '::mvarchar(4) || '123 '::mchar; + ?column? +---------- + asd 123 +(1 row) + +select 'asd '::mvarchar(4) || '123 '::mvarchar; + ?column? +---------- + asd 123 +(1 row) + +select 'asd '::mvarchar(4) || '123 '::mchar(4); + ?column? +---------- + asd 123 +(1 row) + +select 'asd '::mvarchar(4) || '123 '::mvarchar(4); + ?column? +---------- + asd 123 +(1 row) + +select 'asd '::mvarchar(4) || '123'::mchar(4); + ?column? +---------- + asd 123 +(1 row) + +select 'asd '::mvarchar(4) || '123'::mvarchar(4); + ?column? +---------- + asd 123 +(1 row) + +select 1 where 'f'::mchar='F'::mvarchar; + ?column? +---------- + 1 +(1 row) + +select 1 where 'f'::mchar='F '::mvarchar; + ?column? +---------- + 1 +(1 row) + +select 1 where 'f '::mchar='F'::mvarchar; + ?column? +---------- + 1 +(1 row) + +select 1 where 'f '::mchar='F '::mvarchar; + ?column? +---------- + 1 +(1 row) + +select 1 where 'f'::mchar='F'::mvarchar(2); + ?column? +---------- + 1 +(1 row) + +select 1 where 'f'::mchar='F '::mvarchar(2); + ?column? +---------- + 1 +(1 row) + +select 1 where 'f '::mchar='F'::mvarchar(2); + ?column? +---------- + 1 +(1 row) + +select 1 where 'f '::mchar='F '::mvarchar(2); + ?column? +---------- + 1 +(1 row) + +select 1 where 'f'::mchar(2)='F'::mvarchar; + ?column? +---------- + 1 +(1 row) + +select 1 where 'f'::mchar(2)='F '::mvarchar; + ?column? +---------- + 1 +(1 row) + +select 1 where 'f '::mchar(2)='F'::mvarchar; + ?column? +---------- + 1 +(1 row) + +select 1 where 'f '::mchar(2)='F '::mvarchar; + ?column? +---------- + 1 +(1 row) + +select 1 where 'f'::mchar(2)='F'::mvarchar(2); + ?column? +---------- + 1 +(1 row) + +select 1 where 'f'::mchar(2)='F '::mvarchar(2); + ?column? +---------- + 1 +(1 row) + +select 1 where 'f '::mchar(2)='F'::mvarchar(2); + ?column? +---------- + 1 +(1 row) + +select 1 where 'f '::mchar(2)='F '::mvarchar(2); + ?column? +---------- + 1 +(1 row) + +select 1 where 'foo'::mchar='FOO'::mvarchar; + ?column? +---------- + 1 +(1 row) + +select 1 where 'foo'::mchar='FOO '::mvarchar; + ?column? +---------- + 1 +(1 row) + +select 1 where 'foo '::mchar='FOO'::mvarchar; + ?column? +---------- + 1 +(1 row) + +select 1 where 'foo '::mchar='FOO '::mvarchar; + ?column? +---------- + 1 +(1 row) + +select 1 where 'foo'::mchar='FOO'::mvarchar(2); + ?column? +---------- +(0 rows) + +select 1 where 'foo'::mchar='FOO '::mvarchar(2); + ?column? +---------- +(0 rows) + +select 1 where 'foo '::mchar='FOO'::mvarchar(2); + ?column? +---------- +(0 rows) + +select 1 where 'foo '::mchar='FOO '::mvarchar(2); + ?column? +---------- +(0 rows) + +select 1 where 'foo'::mchar(2)='FOO'::mvarchar; + ?column? +---------- +(0 rows) + +select 1 where 'foo'::mchar(2)='FOO '::mvarchar; + ?column? +---------- +(0 rows) + +select 1 where 'foo '::mchar(2)='FOO'::mvarchar; + ?column? +---------- +(0 rows) + +select 1 where 'foo '::mchar(2)='FOO '::mvarchar; + ?column? +---------- +(0 rows) + +select 1 where 'foo'::mchar(2)='FOO'::mvarchar(2); + ?column? +---------- + 1 +(1 row) + +select 1 where 'foo'::mchar(2)='FOO '::mvarchar(2); + ?column? +---------- + 1 +(1 row) + +select 1 where 'foo '::mchar(2)='FOO'::mvarchar(2); + ?column? +---------- + 1 +(1 row) + +select 1 where 'foo '::mchar(2)='FOO '::mvarchar(2); + ?column? +---------- + 1 +(1 row) + +Select 'f'::mchar(1) Union Select 'o'::mvarchar(1); + mchar +------- + f + o +(2 rows) + +Select 'f'::mvarchar(1) Union Select 'o'::mchar(1); + mvarchar +---------- + f + o +(2 rows) + +select * from chvch where ch=vch; + ch | vch +--------------+------------ + No spaces | No spaces + One space | One space + 1 space | 1 space +(3 rows) + +select ch.* from ch, (select 'dEfg'::mvarchar as q) as p where chcol > p.q; + chcol +---------------------------------- + ee + Ee +(2 rows) + +create index qq on ch (chcol); +set enable_seqscan=off; +select ch.* from ch, (select 'dEfg'::mvarchar as q) as p where chcol > p.q; + chcol +---------------------------------- + ee + Ee +(2 rows) + +set enable_seqscan=on; +--\copy chvch to 'results/chvch.dump' binary +--truncate table chvch; +--\copy chvch from 'results/chvch.dump' binary +--test joins +CREATE TABLE a (mchar2 MCHAR(2) NOT NULL); +CREATE TABLE c (mvarchar255 mvarchar NOT NULL); +SELECT * FROM a, c WHERE mchar2 = mvarchar255; + mchar2 | mvarchar255 +--------+------------- +(0 rows) + +SELECT * FROM a, c WHERE mvarchar255 = mchar2; + mchar2 | mvarchar255 +--------+------------- +(0 rows) + +DROP TABLE a; +DROP TABLE c; +select * from (values + ('е'::mchar),('ё'),('еа'),('еб'),('ее'),('еж'),('ёа'),('ёб'),('ёё'),('ёж'),('ёе'),('её')) + z order by 1; + column1 +--------- + е + ё + еа + ёа + еб + ёб + ее + её + ёе + ёё + еж + ёж +(12 rows) + +select 'ё'::mchar = 'е'; + ?column? +---------- + f +(1 row) + +select 'Ё'::mchar = 'Е'; + ?column? +---------- + f +(1 row) + +select 'й'::mchar = 'и'; + ?column? +---------- + f +(1 row) + +select 'Й'::mchar = 'И'; + ?column? +---------- + f +(1 row) + +select mvarchar_icase_cmp('ёа','еб'), mvarchar_icase_cmp('еб','ё'), + mvarchar_icase_cmp('ё', 'ёа'); + mvarchar_icase_cmp | mvarchar_icase_cmp | mvarchar_icase_cmp +--------------------+--------------------+-------------------- + -1 | 1 | -1 +(1 row) + diff --git a/contrib/mchar/expected/mvarchar.out b/contrib/mchar/expected/mvarchar.out new file mode 100644 index 00000000000..5c866b43e71 --- /dev/null +++ b/contrib/mchar/expected/mvarchar.out @@ -0,0 +1,363 @@ +-- I/O tests +select '1'::mvarchar; + mvarchar +---------- + 1 +(1 row) + +select '2 '::mvarchar; + mvarchar +---------- + 2 +(1 row) + +select '10 '::mvarchar; + mvarchar +-------------- + 10 +(1 row) + +select '1'::mvarchar(2); + mvarchar +---------- + 1 +(1 row) + +select '2 '::mvarchar(2); + mvarchar +---------- + 2 +(1 row) + +select '3 '::mvarchar(2); + mvarchar +---------- + 3 +(1 row) + +select '10 '::mvarchar(2); + mvarchar +---------- + 10 +(1 row) + +select ' '::mvarchar(10); + mvarchar +------------ + +(1 row) + +select ' '::mvarchar; + mvarchar +-------------------- + +(1 row) + +-- operations & functions +select length('1'::mvarchar); + length +-------- + 1 +(1 row) + +select length('2 '::mvarchar); + length +-------- + 1 +(1 row) + +select length('10 '::mvarchar); + length +-------- + 2 +(1 row) + +select length('1'::mvarchar(2)); + length +-------- + 1 +(1 row) + +select length('2 '::mvarchar(2)); + length +-------- + 1 +(1 row) + +select length('3 '::mvarchar(2)); + length +-------- + 1 +(1 row) + +select length('10 '::mvarchar(2)); + length +-------- + 2 +(1 row) + +select length(' '::mvarchar(10)); + length +-------- + 0 +(1 row) + +select length(' '::mvarchar); + length +-------- + 0 +(1 row) + +select 'asd'::mvarchar(10) || '>'::mvarchar(10); + ?column? +---------- + asd> +(1 row) + +select length('asd'::mvarchar(10) || '>'::mvarchar(10)); + length +-------- + 4 +(1 row) + +select 'asd'::mvarchar(2) || '>'::mvarchar(10); + ?column? +---------- + as> +(1 row) + +select length('asd'::mvarchar(2) || '>'::mvarchar(10)); + length +-------- + 3 +(1 row) + +-- Comparisons +select 'asdf'::mvarchar = 'aSdf'::mvarchar; + ?column? +---------- + t +(1 row) + +select 'asdf'::mvarchar = 'aSdf '::mvarchar; + ?column? +---------- + t +(1 row) + +select 'asdf'::mvarchar = 'aSdf 1'::mvarchar(4); + ?column? +---------- + t +(1 row) + +select 'asdf'::mvarchar = 'aSdf 1'::mvarchar(5); + ?column? +---------- + t +(1 row) + +select 'asdf'::mvarchar = 'aSdf 1'::mvarchar(6); + ?column? +---------- + f +(1 row) + +select 'asdf'::mvarchar(3) = 'aSdf 1'::mvarchar(5); + ?column? +---------- + f +(1 row) + +select 'asdf'::mvarchar(3) = 'aSdf 1'::mvarchar(3); + ?column? +---------- + t +(1 row) + +select 'asdf'::mvarchar < 'aSdf'::mvarchar; + ?column? +---------- + f +(1 row) + +select 'asdf'::mvarchar < 'aSdf '::mvarchar; + ?column? +---------- + f +(1 row) + +select 'asdf'::mvarchar < 'aSdf 1'::mvarchar(4); + ?column? +---------- + f +(1 row) + +select 'asdf'::mvarchar < 'aSdf 1'::mvarchar(5); + ?column? +---------- + f +(1 row) + +select 'asdf'::mvarchar < 'aSdf 1'::mvarchar(6); + ?column? +---------- + t +(1 row) + +select 'asdf'::mvarchar <= 'aSdf'::mvarchar; + ?column? +---------- + t +(1 row) + +select 'asdf'::mvarchar <= 'aSdf '::mvarchar; + ?column? +---------- + t +(1 row) + +select 'asdf'::mvarchar <= 'aSdf 1'::mvarchar(4); + ?column? +---------- + t +(1 row) + +select 'asdf'::mvarchar <= 'aSdf 1'::mvarchar(5); + ?column? +---------- + t +(1 row) + +select 'asdf'::mvarchar <= 'aSdf 1'::mvarchar(6); + ?column? +---------- + t +(1 row) + +select 'asdf'::mvarchar >= 'aSdf'::mvarchar; + ?column? +---------- + t +(1 row) + +select 'asdf'::mvarchar >= 'aSdf '::mvarchar; + ?column? +---------- + t +(1 row) + +select 'asdf'::mvarchar >= 'aSdf 1'::mvarchar(4); + ?column? +---------- + t +(1 row) + +select 'asdf'::mvarchar >= 'aSdf 1'::mvarchar(5); + ?column? +---------- + t +(1 row) + +select 'asdf'::mvarchar >= 'aSdf 1'::mvarchar(6); + ?column? +---------- + f +(1 row) + +select 'asdf'::mvarchar > 'aSdf'::mvarchar; + ?column? +---------- + f +(1 row) + +select 'asdf'::mvarchar > 'aSdf '::mvarchar; + ?column? +---------- + f +(1 row) + +select 'asdf'::mvarchar > 'aSdf 1'::mvarchar(4); + ?column? +---------- + f +(1 row) + +select 'asdf'::mvarchar > 'aSdf 1'::mvarchar(5); + ?column? +---------- + f +(1 row) + +select 'asdf'::mvarchar > 'aSdf 1'::mvarchar(6); + ?column? +---------- + f +(1 row) + +select max(vch) from chvch; + max +------------ + One space +(1 row) + +select min(vch) from chvch; + min +---------- + 1 space +(1 row) + +select substr('1234567890'::mvarchar, 3) = '34567890' as "34567890"; + 34567890 +---------- + f +(1 row) + +select substr('1234567890'::mvarchar, 4, 3) = '456' as "456"; + 456 +----- + t +(1 row) + +select lower('asdfASDF'::mvarchar); + lower +---------- + asdfasdf +(1 row) + +select upper('asdfASDF'::mvarchar); + upper +---------- + ASDFASDF +(1 row) + +select 'asd'::mvarchar == 'aSd'::mvarchar; + ?column? +---------- + t +(1 row) + +select 'asd'::mvarchar == 'aCd'::mvarchar; + ?column? +---------- + f +(1 row) + +select 'asd'::mvarchar == NULL; + ?column? +---------- + f +(1 row) + +select NULL == 'aCd'::mvarchar; + ?column? +---------- + f +(1 row) + +select NULL::mvarchar == NULL; + ?column? +---------- + t +(1 row) + diff --git a/contrib/mchar/mchar--2.0--2.1.sql b/contrib/mchar/mchar--2.0--2.1.sql new file mode 100644 index 00000000000..a794772f376 --- /dev/null +++ b/contrib/mchar/mchar--2.0--2.1.sql @@ -0,0 +1,2 @@ +ALTER FUNCTION mchar_like(mchar, mvarchar) SUPPORT textlike_support; +ALTER FUNCTION mvarchar_like(mvarchar, mvarchar) SUPPORT textlike_support; diff --git a/contrib/mchar/mchar--2.0.1--2.1.sql b/contrib/mchar/mchar--2.0.1--2.1.sql new file mode 100644 index 00000000000..a794772f376 --- /dev/null +++ b/contrib/mchar/mchar--2.0.1--2.1.sql @@ -0,0 +1,2 @@ +ALTER FUNCTION mchar_like(mchar, mvarchar) SUPPORT textlike_support; +ALTER FUNCTION mvarchar_like(mvarchar, mvarchar) SUPPORT textlike_support; diff --git a/contrib/mchar/mchar--2.1--2.2.sql b/contrib/mchar/mchar--2.1--2.2.sql new file mode 100644 index 00000000000..98689671499 --- /dev/null +++ b/contrib/mchar/mchar--2.1--2.2.sql @@ -0,0 +1,20 @@ +CREATE FUNCTION similar_to_escape(mchar) +RETURNS mchar +AS 'MODULE_PATHNAME', 'mchar_similar_escape' +LANGUAGE C IMMUTABLE; + +CREATE FUNCTION similar_to_escape(mchar, mchar) +RETURNS mchar +AS 'MODULE_PATHNAME', 'mchar_similar_escape' +LANGUAGE C IMMUTABLE; + +CREATE FUNCTION similar_to_escape(mvarchar) +RETURNS mvarchar +AS 'MODULE_PATHNAME', 'mvarchar_similar_escape' +LANGUAGE C IMMUTABLE; + +CREATE FUNCTION similar_to_escape(mvarchar, mvarchar) +RETURNS mvarchar +AS 'MODULE_PATHNAME', 'mvarchar_similar_escape' +LANGUAGE C IMMUTABLE; + diff --git a/contrib/mchar/mchar--2.1.1--2.2.sql b/contrib/mchar/mchar--2.1.1--2.2.sql new file mode 100644 index 00000000000..98689671499 --- /dev/null +++ b/contrib/mchar/mchar--2.1.1--2.2.sql @@ -0,0 +1,20 @@ +CREATE FUNCTION similar_to_escape(mchar) +RETURNS mchar +AS 'MODULE_PATHNAME', 'mchar_similar_escape' +LANGUAGE C IMMUTABLE; + +CREATE FUNCTION similar_to_escape(mchar, mchar) +RETURNS mchar +AS 'MODULE_PATHNAME', 'mchar_similar_escape' +LANGUAGE C IMMUTABLE; + +CREATE FUNCTION similar_to_escape(mvarchar) +RETURNS mvarchar +AS 'MODULE_PATHNAME', 'mvarchar_similar_escape' +LANGUAGE C IMMUTABLE; + +CREATE FUNCTION similar_to_escape(mvarchar, mvarchar) +RETURNS mvarchar +AS 'MODULE_PATHNAME', 'mvarchar_similar_escape' +LANGUAGE C IMMUTABLE; + diff --git a/contrib/mchar/mchar--2.2--2.2.1.sql b/contrib/mchar/mchar--2.2--2.2.1.sql new file mode 100644 index 00000000000..e663aa24a5d --- /dev/null +++ b/contrib/mchar/mchar--2.2--2.2.1.sql @@ -0,0 +1,10 @@ +CREATE OR REPLACE FUNCTION mvarchar_support(internal) + RETURNS internal + AS 'MODULE_PATHNAME' + LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT + PARALLEL SAFE; + +ALTER FUNCTION mvarchar(mvarchar, integer, boolean) + SUPPORT mvarchar_support; + + diff --git a/contrib/mchar/mchar--2.2.1.sql b/contrib/mchar/mchar--2.2.1.sql new file mode 100644 index 00000000000..2f975b64edd --- /dev/null +++ b/contrib/mchar/mchar--2.2.1.sql @@ -0,0 +1,1352 @@ +\echo Use "CREATE EXTENSION mchar" to load this file. \quit + +-- I/O functions + +CREATE FUNCTION mchartypmod_in(cstring[]) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mchartypmod_out(int4) +RETURNS cstring +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mchar_in(cstring) +RETURNS mchar +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mchar_out(mchar) +RETURNS cstring +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mchar_send(mchar) +RETURNS bytea +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mchar_recv(internal) +RETURNS mchar +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE TYPE mchar ( + INTERNALLENGTH = -1, + INPUT = mchar_in, + OUTPUT = mchar_out, + TYPMOD_IN = mchartypmod_in, + TYPMOD_OUT = mchartypmod_out, + RECEIVE = mchar_recv, + SEND = mchar_send, + STORAGE = extended +); + +CREATE FUNCTION mchar(mchar, integer, boolean) +RETURNS mchar +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE CAST (mchar as mchar) +WITH FUNCTION mchar(mchar, integer, boolean) as IMPLICIT; + +CREATE FUNCTION mvarchar_in(cstring) +RETURNS mvarchar +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mvarchar_out(mvarchar) +RETURNS cstring +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mvarchar_send(mvarchar) +RETURNS bytea +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mvarchar_recv(internal) +RETURNS mvarchar +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE TYPE mvarchar ( + INTERNALLENGTH = -1, + INPUT = mvarchar_in, + OUTPUT = mvarchar_out, + TYPMOD_IN = mchartypmod_in, + TYPMOD_OUT = mchartypmod_out, + RECEIVE = mvarchar_recv, + SEND = mvarchar_send, + STORAGE = extended +); + +CREATE FUNCTION mvarchar_support(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT +PARALLEL SAFE; + +CREATE FUNCTION mvarchar(mvarchar, integer, boolean) +RETURNS mvarchar +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT +SUPPORT mvarchar_support; + +CREATE CAST (mvarchar as mvarchar) +WITH FUNCTION mvarchar(mvarchar, integer, boolean) as IMPLICIT; + +--Operations and functions + +CREATE FUNCTION length(mchar) +RETURNS int4 +AS 'MODULE_PATHNAME', 'mchar_length' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION upper(mchar) +RETURNS mchar +AS 'MODULE_PATHNAME', 'mchar_upper' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION lower(mchar) +RETURNS mchar +AS 'MODULE_PATHNAME', 'mchar_lower' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mchar_hash(mchar) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mchar_concat(mchar, mchar) +RETURNS mchar +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE OPERATOR || ( + LEFTARG = mchar, + RIGHTARG = mchar, + PROCEDURE = mchar_concat +); + +CREATE FUNCTION mchar_like(mchar, mvarchar) +RETURNS bool +SUPPORT textlike_support +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mchar_notlike(mchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE OPERATOR ~~ ( + LEFTARG = mchar, + RIGHTARG = mvarchar, + PROCEDURE = mchar_like, + RESTRICT = likesel, + JOIN = likejoinsel, + NEGATOR = '!~~' +); + +CREATE OPERATOR !~~ ( + LEFTARG = mchar, + RIGHTARG = mvarchar, + PROCEDURE = mchar_notlike, + RESTRICT = nlikesel, + JOIN = nlikejoinsel, + NEGATOR = '~~' +); + +CREATE FUNCTION mchar_regexeq(mchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mchar_regexne(mchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE OPERATOR ~ ( + LEFTARG = mchar, + RIGHTARG = mchar, + PROCEDURE = mchar_regexeq, + RESTRICT = regexeqsel, + JOIN = regexeqjoinsel, + NEGATOR = '!~' +); + +CREATE OPERATOR !~ ( + LEFTARG = mchar, + RIGHTARG = mchar, + PROCEDURE = mchar_regexne, + RESTRICT = regexnesel, + JOIN = regexnejoinsel, + NEGATOR = '~' +); + +CREATE FUNCTION similar_escape(mchar, mchar) +RETURNS mchar +AS 'MODULE_PATHNAME', 'mchar_similar_escape' +LANGUAGE C IMMUTABLE; + +CREATE FUNCTION length(mvarchar) +RETURNS int4 +AS 'MODULE_PATHNAME', 'mvarchar_length' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION upper(mvarchar) +RETURNS mvarchar +AS 'MODULE_PATHNAME', 'mvarchar_upper' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION lower(mvarchar) +RETURNS mvarchar +AS 'MODULE_PATHNAME', 'mvarchar_lower' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mvarchar_hash(mvarchar) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mvarchar_concat(mvarchar, mvarchar) +RETURNS mvarchar +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE OPERATOR || ( + LEFTARG = mvarchar, + RIGHTARG = mvarchar, + PROCEDURE = mvarchar_concat +); + +CREATE FUNCTION mvarchar_like(mvarchar, mvarchar) +RETURNS bool +SUPPORT textlike_support +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION like_escape(mvarchar, mvarchar) +RETURNS mvarchar +AS 'MODULE_PATHNAME', 'mvarchar_like_escape' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mvarchar_notlike(mvarchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE OPERATOR ~~ ( + LEFTARG = mvarchar, + RIGHTARG = mvarchar, + PROCEDURE = mvarchar_like, + RESTRICT = likesel, + JOIN = likejoinsel, + NEGATOR = '!~~' +); + +CREATE OPERATOR !~~ ( + LEFTARG = mvarchar, + RIGHTARG = mvarchar, + PROCEDURE = mvarchar_notlike, + RESTRICT = nlikesel, + JOIN = nlikejoinsel, + NEGATOR = '~~' +); + +CREATE FUNCTION mvarchar_regexeq(mvarchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mvarchar_regexne(mvarchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE OPERATOR ~ ( + LEFTARG = mvarchar, + RIGHTARG = mvarchar, + PROCEDURE = mvarchar_regexeq, + RESTRICT = regexeqsel, + JOIN = regexeqjoinsel, + NEGATOR = '!~' +); + +CREATE OPERATOR !~ ( + LEFTARG = mvarchar, + RIGHTARG = mvarchar, + PROCEDURE = mvarchar_regexne, + RESTRICT = regexnesel, + JOIN = regexnejoinsel, + NEGATOR = '~' +); + +CREATE FUNCTION similar_escape(mvarchar, mvarchar) +RETURNS mvarchar +AS 'MODULE_PATHNAME', 'mvarchar_similar_escape' +LANGUAGE C IMMUTABLE; + +CREATE FUNCTION substr (mchar, int4) +RETURNS mchar +AS 'MODULE_PATHNAME', 'mchar_substring_no_len' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION substr (mchar, int4, int4) +RETURNS mchar +AS 'MODULE_PATHNAME', 'mchar_substring' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION substr (mvarchar, int4) +RETURNS mvarchar +AS 'MODULE_PATHNAME', 'mvarchar_substring_no_len' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION substr (mvarchar, int4, int4) +RETURNS mvarchar +AS 'MODULE_PATHNAME', 'mvarchar_substring' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +-- Comparing +-- MCHAR + +CREATE FUNCTION mchar_icase_cmp(mchar, mchar) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mchar_icase_eq(mchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mchar_icase_ne(mchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mchar_icase_lt(mchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mchar_icase_le(mchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mchar_icase_gt(mchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mchar_icase_ge(mchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + + +CREATE OPERATOR < ( + LEFTARG = mchar, + RIGHTARG = mchar, + PROCEDURE = mchar_icase_lt, + COMMUTATOR = '>', + NEGATOR = '>=', + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel +); + +CREATE OPERATOR > ( + LEFTARG = mchar, + RIGHTARG = mchar, + PROCEDURE = mchar_icase_gt, + COMMUTATOR = '<', + NEGATOR = '<=', + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel +); + +CREATE OPERATOR <= ( + LEFTARG = mchar, + RIGHTARG = mchar, + PROCEDURE = mchar_icase_le, + COMMUTATOR = '>=', + NEGATOR = '>', + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel +); + +CREATE OPERATOR >= ( + LEFTARG = mchar, + RIGHTARG = mchar, + PROCEDURE = mchar_icase_ge, + COMMUTATOR = '<=', + NEGATOR = '<', + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel +); + +CREATE OPERATOR = ( + LEFTARG = mchar, + RIGHTARG = mchar, + PROCEDURE = mchar_icase_eq, + COMMUTATOR = '=', + NEGATOR = '<>', + RESTRICT = eqsel, + JOIN = eqjoinsel, + SORT1 = '<', + SORT2 = '<', + HASHES +); + +CREATE OPERATOR <> ( + LEFTARG = mchar, + RIGHTARG = mchar, + PROCEDURE = mchar_icase_ne, + COMMUTATOR = '<>', + NEGATOR = '=', + RESTRICT = neqsel, + JOIN = neqjoinsel +); + +CREATE FUNCTION mchar_case_cmp(mchar, mchar) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mchar_case_eq(mchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mchar_case_ne(mchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mchar_case_lt(mchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mchar_case_le(mchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mchar_case_gt(mchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mchar_case_ge(mchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + + +CREATE OPERATOR &< ( + LEFTARG = mchar, + RIGHTARG = mchar, + PROCEDURE = mchar_case_lt, + COMMUTATOR = '&>', + NEGATOR = '&>=', + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel +); + +CREATE OPERATOR &> ( + LEFTARG = mchar, + RIGHTARG = mchar, + PROCEDURE = mchar_case_gt, + COMMUTATOR = '&<', + NEGATOR = '&<=', + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel +); + +CREATE OPERATOR &<= ( + LEFTARG = mchar, + RIGHTARG = mchar, + PROCEDURE = mchar_case_le, + COMMUTATOR = '&>=', + NEGATOR = '&>', + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel +); + +CREATE OPERATOR &>= ( + LEFTARG = mchar, + RIGHTARG = mchar, + PROCEDURE = mchar_case_ge, + COMMUTATOR = '&<=', + NEGATOR = '&<', + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel +); + +CREATE OPERATOR &= ( + LEFTARG = mchar, + RIGHTARG = mchar, + PROCEDURE = mchar_case_eq, + COMMUTATOR = '&=', + NEGATOR = '&<>', + RESTRICT = eqsel, + JOIN = eqjoinsel, + SORT1 = '&<', + SORT2 = '&<' +); + +CREATE OPERATOR &<> ( + LEFTARG = mchar, + RIGHTARG = mchar, + PROCEDURE = mchar_case_ne, + COMMUTATOR = '&<>', + NEGATOR = '&=', + RESTRICT = neqsel, + JOIN = neqjoinsel +); + +--MVARCHAR + +CREATE FUNCTION mvarchar_icase_cmp(mvarchar, mvarchar) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mvarchar_icase_eq(mvarchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mvarchar_icase_ne(mvarchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mvarchar_icase_lt(mvarchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mvarchar_icase_le(mvarchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mvarchar_icase_gt(mvarchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mvarchar_icase_ge(mvarchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + + +CREATE OPERATOR < ( + LEFTARG = mvarchar, + RIGHTARG = mvarchar, + PROCEDURE = mvarchar_icase_lt, + COMMUTATOR = '>', + NEGATOR = '>=', + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel +); + +CREATE OPERATOR > ( + LEFTARG = mvarchar, + RIGHTARG = mvarchar, + PROCEDURE = mvarchar_icase_gt, + COMMUTATOR = '<', + NEGATOR = '<=', + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel +); + +CREATE OPERATOR <= ( + LEFTARG = mvarchar, + RIGHTARG = mvarchar, + PROCEDURE = mvarchar_icase_le, + COMMUTATOR = '>=', + NEGATOR = '>', + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel +); + +CREATE OPERATOR >= ( + LEFTARG = mvarchar, + RIGHTARG = mvarchar, + PROCEDURE = mvarchar_icase_ge, + COMMUTATOR = '<=', + NEGATOR = '<', + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel +); + +CREATE OPERATOR = ( + LEFTARG = mvarchar, + RIGHTARG = mvarchar, + PROCEDURE = mvarchar_icase_eq, + COMMUTATOR = '=', + NEGATOR = '<>', + RESTRICT = eqsel, + JOIN = eqjoinsel, + SORT1 = '<', + SORT2 = '<', + HASHES +); + +CREATE OPERATOR <> ( + LEFTARG = mvarchar, + RIGHTARG = mvarchar, + PROCEDURE = mvarchar_icase_ne, + COMMUTATOR = '<>', + NEGATOR = '=', + RESTRICT = neqsel, + JOIN = neqjoinsel +); + +CREATE FUNCTION mvarchar_case_cmp(mvarchar, mvarchar) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mvarchar_case_eq(mvarchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mvarchar_case_ne(mvarchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mvarchar_case_lt(mvarchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mvarchar_case_le(mvarchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mvarchar_case_gt(mvarchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mvarchar_case_ge(mvarchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + + +CREATE OPERATOR &< ( + LEFTARG = mvarchar, + RIGHTARG = mvarchar, + PROCEDURE = mvarchar_case_lt, + COMMUTATOR = '&>', + NEGATOR = '&>=', + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel +); + +CREATE OPERATOR &> ( + LEFTARG = mvarchar, + RIGHTARG = mvarchar, + PROCEDURE = mvarchar_case_gt, + COMMUTATOR = '&<', + NEGATOR = '&<=', + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel +); + +CREATE OPERATOR &<= ( + LEFTARG = mvarchar, + RIGHTARG = mvarchar, + PROCEDURE = mvarchar_case_le, + COMMUTATOR = '&>=', + NEGATOR = '&>', + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel +); + +CREATE OPERATOR &>= ( + LEFTARG = mvarchar, + RIGHTARG = mvarchar, + PROCEDURE = mvarchar_case_ge, + COMMUTATOR = '&<=', + NEGATOR = '&<', + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel +); + +CREATE OPERATOR &= ( + LEFTARG = mvarchar, + RIGHTARG = mvarchar, + PROCEDURE = mvarchar_case_eq, + COMMUTATOR = '&=', + NEGATOR = '&<>', + RESTRICT = eqsel, + JOIN = eqjoinsel, + SORT1 = '&<', + SORT2 = '&<' +); + +CREATE OPERATOR &<> ( + LEFTARG = mvarchar, + RIGHTARG = mvarchar, + PROCEDURE = mvarchar_case_ne, + COMMUTATOR = '&<>', + NEGATOR = '&=', + RESTRICT = neqsel, + JOIN = neqjoinsel +); + +-- MCHAR <> MVARCHAR + +CREATE FUNCTION mc_mv_icase_cmp(mchar, mvarchar) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mc_mv_icase_eq(mchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mc_mv_icase_ne(mchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mc_mv_icase_lt(mchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mc_mv_icase_le(mchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mc_mv_icase_gt(mchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mc_mv_icase_ge(mchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + + +CREATE OPERATOR < ( + LEFTARG = mchar, + RIGHTARG = mvarchar, + PROCEDURE = mc_mv_icase_lt, + COMMUTATOR = '>', + NEGATOR = '>=', + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel +); + +CREATE OPERATOR > ( + LEFTARG = mchar, + RIGHTARG = mvarchar, + PROCEDURE = mc_mv_icase_gt, + COMMUTATOR = '<', + NEGATOR = '<=', + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel +); + +CREATE OPERATOR <= ( + LEFTARG = mchar, + RIGHTARG = mvarchar, + PROCEDURE = mc_mv_icase_le, + COMMUTATOR = '>=', + NEGATOR = '>', + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel +); + +CREATE OPERATOR >= ( + LEFTARG = mchar, + RIGHTARG = mvarchar, + PROCEDURE = mc_mv_icase_ge, + COMMUTATOR = '<=', + NEGATOR = '<', + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel +); + +CREATE OPERATOR = ( + LEFTARG = mchar, + RIGHTARG = mvarchar, + PROCEDURE = mc_mv_icase_eq, + COMMUTATOR = '=', + NEGATOR = '<>', + RESTRICT = eqsel, + JOIN = eqjoinsel, + SORT1 = '<', + SORT2 = '<' +); + +CREATE OPERATOR <> ( + LEFTARG = mchar, + RIGHTARG = mvarchar, + PROCEDURE = mc_mv_icase_ne, + COMMUTATOR = '<>', + NEGATOR = '=', + RESTRICT = neqsel, + JOIN = neqjoinsel +); + +CREATE FUNCTION mc_mv_case_cmp(mchar, mvarchar) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mc_mv_case_eq(mchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mc_mv_case_ne(mchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mc_mv_case_lt(mchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mc_mv_case_le(mchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mc_mv_case_gt(mchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mc_mv_case_ge(mchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + + +CREATE OPERATOR &< ( + LEFTARG = mchar, + RIGHTARG = mvarchar, + PROCEDURE = mc_mv_case_lt, + COMMUTATOR = '&>', + NEGATOR = '&>=', + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel +); + +CREATE OPERATOR &> ( + LEFTARG = mchar, + RIGHTARG = mvarchar, + PROCEDURE = mc_mv_case_gt, + COMMUTATOR = '&<', + NEGATOR = '&<=', + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel +); + +CREATE OPERATOR &<= ( + LEFTARG = mchar, + RIGHTARG = mvarchar, + PROCEDURE = mc_mv_case_le, + COMMUTATOR = '&>=', + NEGATOR = '&>', + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel +); + +CREATE OPERATOR &>= ( + LEFTARG = mchar, + RIGHTARG = mvarchar, + PROCEDURE = mc_mv_case_ge, + COMMUTATOR = '&<=', + NEGATOR = '&<', + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel +); + +CREATE OPERATOR &= ( + LEFTARG = mchar, + RIGHTARG = mvarchar, + PROCEDURE = mc_mv_case_eq, + COMMUTATOR = '&=', + NEGATOR = '&<>', + RESTRICT = eqsel, + JOIN = eqjoinsel, + SORT1 = '&<', + SORT2 = '&<' +); + +CREATE OPERATOR &<> ( + LEFTARG = mchar, + RIGHTARG = mvarchar, + PROCEDURE = mc_mv_case_ne, + COMMUTATOR = '&<>', + NEGATOR = '&=', + RESTRICT = neqsel, + JOIN = neqjoinsel +); + +-- MVARCHAR <> MCHAR + +CREATE FUNCTION mv_mc_icase_cmp(mvarchar, mchar) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mv_mc_icase_eq(mvarchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mv_mc_icase_ne(mvarchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mv_mc_icase_lt(mvarchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mv_mc_icase_le(mvarchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mv_mc_icase_gt(mvarchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mv_mc_icase_ge(mvarchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + + +CREATE OPERATOR < ( + LEFTARG = mvarchar, + RIGHTARG = mchar, + PROCEDURE = mv_mc_icase_lt, + COMMUTATOR = '>', + NEGATOR = '>=', + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel +); + +CREATE OPERATOR > ( + LEFTARG = mvarchar, + RIGHTARG = mchar, + PROCEDURE = mv_mc_icase_gt, + COMMUTATOR = '<', + NEGATOR = '<=', + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel +); + +CREATE OPERATOR <= ( + LEFTARG = mvarchar, + RIGHTARG = mchar, + PROCEDURE = mv_mc_icase_le, + COMMUTATOR = '>=', + NEGATOR = '>', + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel +); + +CREATE OPERATOR >= ( + LEFTARG = mvarchar, + RIGHTARG = mchar, + PROCEDURE = mv_mc_icase_ge, + COMMUTATOR = '<=', + NEGATOR = '<', + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel +); + +CREATE OPERATOR = ( + LEFTARG = mvarchar, + RIGHTARG = mchar, + PROCEDURE = mv_mc_icase_eq, + COMMUTATOR = '=', + NEGATOR = '<>', + RESTRICT = eqsel, + JOIN = eqjoinsel, + SORT1 = '<', + SORT2 = '<' +); + +CREATE OPERATOR <> ( + LEFTARG = mvarchar, + RIGHTARG = mchar, + PROCEDURE = mv_mc_icase_ne, + COMMUTATOR = '<>', + NEGATOR = '=', + RESTRICT = neqsel, + JOIN = neqjoinsel +); + +CREATE FUNCTION mv_mc_case_cmp(mvarchar, mchar) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mv_mc_case_eq(mvarchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mv_mc_case_ne(mvarchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mv_mc_case_lt(mvarchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mv_mc_case_le(mvarchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mv_mc_case_gt(mvarchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mv_mc_case_ge(mvarchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + + +CREATE OPERATOR &< ( + LEFTARG = mvarchar, + RIGHTARG = mchar, + PROCEDURE = mv_mc_case_lt, + COMMUTATOR = '&>', + NEGATOR = '&>=', + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel +); + +CREATE OPERATOR &> ( + LEFTARG = mvarchar, + RIGHTARG = mchar, + PROCEDURE = mv_mc_case_gt, + COMMUTATOR = '&<', + NEGATOR = '&<=', + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel +); + +CREATE OPERATOR &<= ( + LEFTARG = mvarchar, + RIGHTARG = mchar, + PROCEDURE = mv_mc_case_le, + COMMUTATOR = '&>=', + NEGATOR = '&>', + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel +); + +CREATE OPERATOR &>= ( + LEFTARG = mvarchar, + RIGHTARG = mchar, + PROCEDURE = mv_mc_case_ge, + COMMUTATOR = '&<=', + NEGATOR = '&<', + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel +); + +CREATE OPERATOR &= ( + LEFTARG = mvarchar, + RIGHTARG = mchar, + PROCEDURE = mv_mc_case_eq, + COMMUTATOR = '&=', + NEGATOR = '&<>', + RESTRICT = eqsel, + JOIN = eqjoinsel, + SORT1 = '&<', + SORT2 = '&<' +); + +CREATE OPERATOR &<> ( + LEFTARG = mvarchar, + RIGHTARG = mchar, + PROCEDURE = mv_mc_case_ne, + COMMUTATOR = '&<>', + NEGATOR = '&=', + RESTRICT = neqsel, + JOIN = neqjoinsel +); + +-- MCHAR - VARCHAR operations + +CREATE FUNCTION mchar_mvarchar_concat(mchar, mvarchar) +RETURNS mvarchar +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE OPERATOR || ( + LEFTARG = mchar, + RIGHTARG = mvarchar, + PROCEDURE = mchar_mvarchar_concat +); + +CREATE FUNCTION mvarchar_mchar_concat(mvarchar, mchar) +RETURNS mvarchar +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE OPERATOR || ( + LEFTARG = mvarchar, + RIGHTARG = mchar, + PROCEDURE = mvarchar_mchar_concat +); + +CREATE FUNCTION mvarchar_mchar(mvarchar, integer, boolean) +RETURNS mchar +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE CAST (mvarchar as mchar) +WITH FUNCTION mvarchar_mchar(mvarchar, integer, boolean) as IMPLICIT; + +CREATE FUNCTION mchar_mvarchar(mchar, integer, boolean) +RETURNS mvarchar +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE CAST (mchar as mvarchar) +WITH FUNCTION mchar_mvarchar(mchar, integer, boolean) as IMPLICIT; + +-- Aggregates + +CREATE FUNCTION mchar_larger(mchar, mchar) +RETURNS mchar +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE AGGREGATE max ( + BASETYPE = mchar, + SFUNC = mchar_larger, + STYPE = mchar, + SORTOP = '>' +); + +CREATE FUNCTION mchar_smaller(mchar, mchar) +RETURNS mchar +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE AGGREGATE min ( + BASETYPE = mchar, + SFUNC = mchar_smaller, + STYPE = mchar, + SORTOP = '<' +); + +CREATE FUNCTION mvarchar_larger(mvarchar, mvarchar) +RETURNS mvarchar +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE AGGREGATE max ( + BASETYPE = mvarchar, + SFUNC = mvarchar_larger, + STYPE = mvarchar, + SORTOP = '>' +); + +CREATE FUNCTION mvarchar_smaller(mvarchar, mvarchar) +RETURNS mvarchar +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE AGGREGATE min ( + BASETYPE = mvarchar, + SFUNC = mvarchar_smaller, + STYPE = mvarchar, + SORTOP = '<' +); + +-- B-tree support +CREATE OPERATOR FAMILY icase_ops USING btree; +CREATE OPERATOR FAMILY case_ops USING btree; + +CREATE OPERATOR CLASS mchar_icase_ops +DEFAULT FOR TYPE mchar USING btree FAMILY icase_ops AS + OPERATOR 1 < , + OPERATOR 2 <= , + OPERATOR 3 = , + OPERATOR 4 >= , + OPERATOR 5 > , + FUNCTION 1 mchar_icase_cmp(mchar, mchar), + OPERATOR 1 < (mchar, mvarchar), + OPERATOR 2 <= (mchar, mvarchar), + OPERATOR 3 = (mchar, mvarchar), + OPERATOR 4 >= (mchar, mvarchar), + OPERATOR 5 > (mchar, mvarchar), + FUNCTION 1 mc_mv_icase_cmp(mchar, mvarchar); + +CREATE OPERATOR CLASS mchar_case_ops +FOR TYPE mchar USING btree FAMILY case_ops AS + OPERATOR 1 &< , + OPERATOR 2 &<= , + OPERATOR 3 &= , + OPERATOR 4 &>= , + OPERATOR 5 &> , + FUNCTION 1 mchar_case_cmp(mchar, mchar), + OPERATOR 1 &< (mchar, mvarchar), + OPERATOR 2 &<= (mchar, mvarchar), + OPERATOR 3 &= (mchar, mvarchar), + OPERATOR 4 &>= (mchar, mvarchar), + OPERATOR 5 &> (mchar, mvarchar), + FUNCTION 1 mc_mv_case_cmp(mchar, mvarchar); + +CREATE OPERATOR CLASS mchar_icase_ops +DEFAULT FOR TYPE mchar USING hash AS + OPERATOR 1 = , + FUNCTION 1 mchar_hash(mchar); + +CREATE OPERATOR CLASS mvarchar_icase_ops +DEFAULT FOR TYPE mvarchar USING btree FAMILY icase_ops AS + OPERATOR 1 < , + OPERATOR 2 <= , + OPERATOR 3 = , + OPERATOR 4 >= , + OPERATOR 5 > , + FUNCTION 1 mvarchar_icase_cmp(mvarchar, mvarchar), + OPERATOR 1 < (mvarchar, mchar), + OPERATOR 2 <= (mvarchar, mchar), + OPERATOR 3 = (mvarchar, mchar), + OPERATOR 4 >= (mvarchar, mchar), + OPERATOR 5 > (mvarchar, mchar), + FUNCTION 1 mv_mc_icase_cmp(mvarchar, mchar); + +CREATE OPERATOR CLASS mvarchar_case_ops +FOR TYPE mvarchar USING btree FAMILY case_ops AS + OPERATOR 1 &< , + OPERATOR 2 &<= , + OPERATOR 3 &= , + OPERATOR 4 &>= , + OPERATOR 5 &> , + FUNCTION 1 mvarchar_case_cmp(mvarchar, mvarchar), + OPERATOR 1 &< (mvarchar, mchar), + OPERATOR 2 &<= (mvarchar, mchar), + OPERATOR 3 &= (mvarchar, mchar), + OPERATOR 4 &>= (mvarchar, mchar), + OPERATOR 5 &> (mvarchar, mchar), + FUNCTION 1 mv_mc_case_cmp(mvarchar, mchar); + +CREATE OPERATOR CLASS mvarchar_icase_ops +DEFAULT FOR TYPE mvarchar USING hash AS + OPERATOR 1 = , + FUNCTION 1 mvarchar_hash(mvarchar); + + +-- Index support for LIKE + +CREATE FUNCTION mchar_pattern_fixed_prefix(internal, internal, internal) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mchar_greaterstring(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE OR REPLACE FUNCTION isfulleq_mchar(mchar, mchar) +RETURNS bool AS 'MODULE_PATHNAME' +LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + +CREATE OR REPLACE FUNCTION fullhash_mchar(mchar) +RETURNS int4 AS 'MODULE_PATHNAME' +LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + + +CREATE OPERATOR == ( + LEFTARG = mchar, + RIGHTARG = mchar, + PROCEDURE = isfulleq_mchar, + COMMUTATOR = '==', + RESTRICT = eqsel, + JOIN = eqjoinsel, + HASHES +); + +CREATE OPERATOR CLASS mchar_fill_ops + FOR TYPE mchar USING hash AS + OPERATOR 1 ==, + FUNCTION 1 fullhash_mchar(mchar); + +CREATE OR REPLACE FUNCTION isfulleq_mvarchar(mvarchar, mvarchar) +RETURNS bool AS 'MODULE_PATHNAME' +LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + +CREATE OR REPLACE FUNCTION fullhash_mvarchar(mvarchar) +RETURNS int4 AS 'MODULE_PATHNAME' +LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + + +CREATE OPERATOR == ( + LEFTARG = mvarchar, + RIGHTARG = mvarchar, + PROCEDURE = isfulleq_mvarchar, + COMMUTATOR = '==', + RESTRICT = eqsel, + JOIN = eqjoinsel, + HASHES +); + +CREATE OPERATOR CLASS mvarchar_fill_ops + FOR TYPE mvarchar USING hash AS + OPERATOR 1 ==, + FUNCTION 1 fullhash_mvarchar(mvarchar); + +CREATE FUNCTION similar_to_escape(mchar) + RETURNS mchar + AS 'MODULE_PATHNAME', 'mchar_similar_escape' + LANGUAGE C IMMUTABLE; + +CREATE FUNCTION similar_to_escape(mchar, mchar) + RETURNS mchar + AS 'MODULE_PATHNAME', 'mchar_similar_escape' + LANGUAGE C IMMUTABLE; + +CREATE FUNCTION similar_to_escape(mvarchar) + RETURNS mvarchar + AS 'MODULE_PATHNAME', 'mvarchar_similar_escape' + LANGUAGE C IMMUTABLE; + +CREATE FUNCTION similar_to_escape(mvarchar, mvarchar) + RETURNS mvarchar + AS 'MODULE_PATHNAME', 'mvarchar_similar_escape' + LANGUAGE C IMMUTABLE; + diff --git a/contrib/mchar/mchar--unpackaged--2.0.sql b/contrib/mchar/mchar--unpackaged--2.0.sql new file mode 100644 index 00000000000..1acc4ccec1e --- /dev/null +++ b/contrib/mchar/mchar--unpackaged--2.0.sql @@ -0,0 +1,404 @@ +\echo Use "CREATE EXTENSION mchar FROM unpackaged" to load this file. \quit + +-- I/O functions + +ALTER EXTENSION mchar ADD FUNCTION mchartypmod_in(cstring[]); + +ALTER EXTENSION mchar ADD FUNCTION mchartypmod_out(int4); + +ALTER EXTENSION mchar ADD FUNCTION mchar_in(cstring); + +ALTER EXTENSION mchar ADD FUNCTION mchar_out(mchar); + +ALTER EXTENSION mchar ADD FUNCTION mchar_send(mchar); + +ALTER EXTENSION mchar ADD FUNCTION mchar_recv(internal); + +ALTER EXTENSION mchar ADD TYPE mchar; + +ALTER EXTENSION mchar ADD FUNCTION mchar(mchar, integer, boolean); + +ALTER EXTENSION mchar ADD CAST (mchar as mchar); + +ALTER EXTENSION mchar ADD FUNCTION mvarchar_in(cstring); + +ALTER EXTENSION mchar ADD FUNCTION mvarchar_out(mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mvarchar_send(mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mvarchar_recv(internal); + +ALTER EXTENSION mchar ADD TYPE mvarchar; + +ALTER EXTENSION mchar ADD FUNCTION mvarchar(mvarchar, integer, boolean); + +ALTER EXTENSION mchar ADD CAST (mvarchar as mvarchar); + +--Operations and functions + +ALTER EXTENSION mchar ADD FUNCTION length(mchar); + +ALTER EXTENSION mchar ADD FUNCTION upper(mchar); + +ALTER EXTENSION mchar ADD FUNCTION lower(mchar); + +ALTER EXTENSION mchar ADD FUNCTION mchar_hash(mchar); + +ALTER EXTENSION mchar ADD FUNCTION mchar_concat(mchar, mchar); + +ALTER EXTENSION mchar ADD OPERATOR || (mchar, mchar); + +ALTER EXTENSION mchar ADD FUNCTION mchar_like(mchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mchar_notlike(mchar, mvarchar); + +ALTER EXTENSION mchar ADD OPERATOR ~~ (mchar, mvarchar); + +ALTER EXTENSION mchar ADD OPERATOR !~~ (mchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mchar_regexeq(mchar, mchar); + +ALTER EXTENSION mchar ADD FUNCTION mchar_regexne(mchar, mchar); + +ALTER EXTENSION mchar ADD OPERATOR ~ (mchar, mchar); + +ALTER EXTENSION mchar ADD OPERATOR !~ (mchar, mchar); + +ALTER EXTENSION mchar ADD FUNCTION similar_escape(mchar, mchar); + +ALTER EXTENSION mchar ADD FUNCTION length(mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION upper(mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION lower(mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mvarchar_hash(mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mvarchar_concat(mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD OPERATOR || (mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mvarchar_like(mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION like_escape(mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mvarchar_notlike(mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD OPERATOR ~~ (mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD OPERATOR !~~ (mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mvarchar_regexeq(mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mvarchar_regexne(mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD OPERATOR ~ (mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD OPERATOR !~ (mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION similar_escape(mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION substr (mchar, int4); + +ALTER EXTENSION mchar ADD FUNCTION substr (mchar, int4, int4); + +ALTER EXTENSION mchar ADD FUNCTION substr (mvarchar, int4); + +ALTER EXTENSION mchar ADD FUNCTION substr (mvarchar, int4, int4); + +-- Comparing +-- MCHAR + +ALTER EXTENSION mchar ADD FUNCTION mchar_icase_cmp(mchar, mchar); + +ALTER EXTENSION mchar ADD FUNCTION mchar_icase_eq(mchar, mchar); + +ALTER EXTENSION mchar ADD FUNCTION mchar_icase_ne(mchar, mchar); + +ALTER EXTENSION mchar ADD FUNCTION mchar_icase_lt(mchar, mchar); + +ALTER EXTENSION mchar ADD FUNCTION mchar_icase_le(mchar, mchar); + +ALTER EXTENSION mchar ADD FUNCTION mchar_icase_gt(mchar, mchar); + +ALTER EXTENSION mchar ADD FUNCTION mchar_icase_ge(mchar, mchar); + +ALTER EXTENSION mchar ADD OPERATOR < (mchar, mchar); + +ALTER EXTENSION mchar ADD OPERATOR > (mchar, mchar); + +ALTER EXTENSION mchar ADD OPERATOR <= (mchar, mchar); + +ALTER EXTENSION mchar ADD OPERATOR >= (mchar, mchar); + +ALTER EXTENSION mchar ADD OPERATOR = (mchar, mchar); + +ALTER EXTENSION mchar ADD OPERATOR <> (mchar, mchar); + +ALTER EXTENSION mchar ADD FUNCTION mchar_case_cmp(mchar, mchar); + +ALTER EXTENSION mchar ADD FUNCTION mchar_case_eq(mchar, mchar); + +ALTER EXTENSION mchar ADD FUNCTION mchar_case_ne(mchar, mchar); + +ALTER EXTENSION mchar ADD FUNCTION mchar_case_lt(mchar, mchar); + +ALTER EXTENSION mchar ADD FUNCTION mchar_case_le(mchar, mchar); + +ALTER EXTENSION mchar ADD FUNCTION mchar_case_gt(mchar, mchar); + +ALTER EXTENSION mchar ADD FUNCTION mchar_case_ge(mchar, mchar); + + +ALTER EXTENSION mchar ADD OPERATOR &< (mchar, mchar); + +ALTER EXTENSION mchar ADD OPERATOR &> (mchar, mchar); + +ALTER EXTENSION mchar ADD OPERATOR &<= (mchar, mchar); + +ALTER EXTENSION mchar ADD OPERATOR &>= (mchar, mchar); + +ALTER EXTENSION mchar ADD OPERATOR &= (mchar, mchar); + +ALTER EXTENSION mchar ADD OPERATOR &<> (mchar, mchar); + +--MVARCHAR + +ALTER EXTENSION mchar ADD FUNCTION mvarchar_icase_cmp(mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mvarchar_icase_eq(mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mvarchar_icase_ne(mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mvarchar_icase_lt(mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mvarchar_icase_le(mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mvarchar_icase_gt(mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mvarchar_icase_ge(mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD OPERATOR < (mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD OPERATOR > (mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD OPERATOR <= (mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD OPERATOR >= (mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD OPERATOR = (mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD OPERATOR <> (mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mvarchar_case_cmp(mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mvarchar_case_eq(mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mvarchar_case_ne(mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mvarchar_case_lt(mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mvarchar_case_le(mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mvarchar_case_gt(mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mvarchar_case_ge(mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD OPERATOR &< (mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD OPERATOR &> (mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD OPERATOR &<= (mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD OPERATOR &>= (mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD OPERATOR &= (mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD OPERATOR &<> (mvarchar, mvarchar); + +-- MCHAR <> MVARCHAR + +ALTER EXTENSION mchar ADD FUNCTION mc_mv_icase_cmp(mchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mc_mv_icase_eq(mchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mc_mv_icase_ne(mchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mc_mv_icase_lt(mchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mc_mv_icase_le(mchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mc_mv_icase_gt(mchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mc_mv_icase_ge(mchar, mvarchar); + +ALTER EXTENSION mchar ADD OPERATOR < (mchar, mvarchar); + +ALTER EXTENSION mchar ADD OPERATOR > (mchar, mvarchar); + +ALTER EXTENSION mchar ADD OPERATOR <= (mchar, mvarchar); + +ALTER EXTENSION mchar ADD OPERATOR >= (mchar, mvarchar); + +ALTER EXTENSION mchar ADD OPERATOR = (mchar, mvarchar); + +ALTER EXTENSION mchar ADD OPERATOR <> (mchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mc_mv_case_cmp(mchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mc_mv_case_eq(mchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mc_mv_case_ne(mchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mc_mv_case_lt(mchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mc_mv_case_le(mchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mc_mv_case_gt(mchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mc_mv_case_ge(mchar, mvarchar); + +ALTER EXTENSION mchar ADD OPERATOR &< (mchar, mvarchar); + +ALTER EXTENSION mchar ADD OPERATOR &> (mchar, mvarchar); + +ALTER EXTENSION mchar ADD OPERATOR &<= (mchar, mvarchar); + +ALTER EXTENSION mchar ADD OPERATOR &>= (mchar, mvarchar); + +ALTER EXTENSION mchar ADD OPERATOR &= (mchar, mvarchar); + +ALTER EXTENSION mchar ADD OPERATOR &<> (mchar, mvarchar); + +-- MVARCHAR <> MCHAR + +ALTER EXTENSION mchar ADD FUNCTION mv_mc_icase_cmp(mvarchar, mchar); + +ALTER EXTENSION mchar ADD FUNCTION mv_mc_icase_eq(mvarchar, mchar); + +ALTER EXTENSION mchar ADD FUNCTION mv_mc_icase_ne(mvarchar, mchar); + +ALTER EXTENSION mchar ADD FUNCTION mv_mc_icase_lt(mvarchar, mchar); + +ALTER EXTENSION mchar ADD FUNCTION mv_mc_icase_le(mvarchar, mchar); + +ALTER EXTENSION mchar ADD FUNCTION mv_mc_icase_gt(mvarchar, mchar); + +ALTER EXTENSION mchar ADD FUNCTION mv_mc_icase_ge(mvarchar, mchar); + +ALTER EXTENSION mchar ADD OPERATOR < (mvarchar, mchar); + +ALTER EXTENSION mchar ADD OPERATOR > (mvarchar, mchar); + +ALTER EXTENSION mchar ADD OPERATOR <= (mvarchar, mchar); + +ALTER EXTENSION mchar ADD OPERATOR >= (mvarchar, mchar); + +ALTER EXTENSION mchar ADD OPERATOR = (mvarchar, mchar); + +ALTER EXTENSION mchar ADD OPERATOR <> (mvarchar, mchar); + +ALTER EXTENSION mchar ADD FUNCTION mv_mc_case_cmp(mvarchar, mchar); + +ALTER EXTENSION mchar ADD FUNCTION mv_mc_case_eq(mvarchar, mchar); + +ALTER EXTENSION mchar ADD FUNCTION mv_mc_case_ne(mvarchar, mchar); + +ALTER EXTENSION mchar ADD FUNCTION mv_mc_case_lt(mvarchar, mchar); + +ALTER EXTENSION mchar ADD FUNCTION mv_mc_case_le(mvarchar, mchar); + +ALTER EXTENSION mchar ADD FUNCTION mv_mc_case_gt(mvarchar, mchar); + +ALTER EXTENSION mchar ADD FUNCTION mv_mc_case_ge(mvarchar, mchar); + +ALTER EXTENSION mchar ADD OPERATOR &< (mvarchar, mchar); + +ALTER EXTENSION mchar ADD OPERATOR &> (mvarchar, mchar); + +ALTER EXTENSION mchar ADD OPERATOR &<= (mvarchar, mchar); + +ALTER EXTENSION mchar ADD OPERATOR &>= (mvarchar, mchar); + +ALTER EXTENSION mchar ADD OPERATOR &= (mvarchar, mchar); + +ALTER EXTENSION mchar ADD OPERATOR &<> (mvarchar, mchar); + +-- MCHAR - VARCHAR operations + +ALTER EXTENSION mchar ADD FUNCTION mchar_mvarchar_concat(mchar, mvarchar); + +ALTER EXTENSION mchar ADD OPERATOR || (mchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mvarchar_mchar_concat(mvarchar, mchar); + +ALTER EXTENSION mchar ADD OPERATOR || (mvarchar, mchar); + +ALTER EXTENSION mchar ADD FUNCTION mvarchar_mchar(mvarchar, integer, boolean); + +ALTER EXTENSION mchar ADD CAST (mvarchar as mchar); + +ALTER EXTENSION mchar ADD FUNCTION mchar_mvarchar(mchar, integer, boolean); + +ALTER EXTENSION mchar ADD CAST (mchar as mvarchar); + +-- Aggregates + +ALTER EXTENSION mchar ADD FUNCTION mchar_larger(mchar, mchar); + +ALTER EXTENSION mchar ADD AGGREGATE max (mchar); + +ALTER EXTENSION mchar ADD FUNCTION mchar_smaller(mchar, mchar); + +ALTER EXTENSION mchar ADD AGGREGATE min (mchar); + +ALTER EXTENSION mchar ADD FUNCTION mvarchar_larger(mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD AGGREGATE max (mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mvarchar_smaller(mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD AGGREGATE min (mvarchar); + +-- B-tree support +ALTER EXTENSION mchar ADD OPERATOR FAMILY icase_ops USING btree; + +ALTER EXTENSION mchar ADD OPERATOR FAMILY case_ops USING btree; + +ALTER EXTENSION mchar ADD OPERATOR CLASS mchar_icase_ops USING btree; + +ALTER EXTENSION mchar ADD OPERATOR CLASS mchar_case_ops USING btree; + +ALTER EXTENSION mchar ADD OPERATOR CLASS mchar_icase_ops USING hash; + +ALTER EXTENSION mchar ADD OPERATOR CLASS mvarchar_icase_ops USING btree; + +ALTER EXTENSION mchar ADD OPERATOR CLASS mvarchar_case_ops USING btree; + +ALTER EXTENSION mchar ADD OPERATOR CLASS mvarchar_icase_ops USING hash; + + +-- Index support for LIKE + +--mchar_pattern_fixed_prefix could be with wrong number of arguments +ALTER EXTENSION mchar ADD FUNCTION mchar_pattern_fixed_prefix; + +ALTER EXTENSION mchar ADD FUNCTION mchar_greaterstring(internal); + +ALTER EXTENSION mchar ADD FUNCTION isfulleq_mchar(mchar, mchar); + +ALTER EXTENSION mchar ADD FUNCTION fullhash_mchar(mchar); + +ALTER EXTENSION mchar ADD OPERATOR == (mchar, mchar); + +ALTER EXTENSION mchar ADD OPERATOR CLASS mchar_fill_ops USING hash; + +ALTER EXTENSION mchar ADD FUNCTION isfulleq_mvarchar(mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION fullhash_mvarchar(mvarchar); + +ALTER EXTENSION mchar ADD OPERATOR == (mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD OPERATOR CLASS mvarchar_fill_ops USING hash; + + diff --git a/contrib/mchar/mchar.control b/contrib/mchar/mchar.control new file mode 100644 index 00000000000..02668a5d617 --- /dev/null +++ b/contrib/mchar/mchar.control @@ -0,0 +1,6 @@ +# mchar extension +comment = 'SQL Server text type' +default_version = '2.2.1' +module_pathname = '$libdir/mchar' +relocatable = true +trusted = true diff --git a/contrib/mchar/mchar.h b/contrib/mchar/mchar.h new file mode 100644 index 00000000000..a88a0e1eb76 --- /dev/null +++ b/contrib/mchar/mchar.h @@ -0,0 +1,63 @@ +#ifndef __MCHAR_H__ +#define __MCHAR_H__ + +#include "postgres.h" +#include "mb/pg_wchar.h" +#include "utils/builtins.h" +#include "unicode/uchar.h" +#include "unicode/ustring.h" + +typedef struct { + int32 len; + int32 typmod; + UChar data[1]; +} MChar; + +#define MCHARHDRSZ offsetof(MChar, data) +#define MCHARLENGTH(m) ( VARSIZE(m)-MCHARHDRSZ ) +#define UCHARLENGTH(m) ( MCHARLENGTH(m)/sizeof(UChar) ) + +#define DatumGetMChar(m) ((MChar*)DatumGetPointer(m)) +#define MCharGetDatum(m) PointerGetDatum(m) + +#define PG_GETARG_MCHAR(n) DatumGetMChar(PG_DETOAST_DATUM(PG_GETARG_DATUM(n))) +#define PG_GETARG_MCHAR_COPY(n) DatumGetMChar(PG_DETOAST_DATUM_COPY(PG_GETARG_DATUM(n))) + +#define PG_RETURN_MCHAR(m) PG_RETURN_POINTER(m) + +typedef struct { + int32 len; + UChar data[1]; +} MVarChar; + +#define MVARCHARHDRSZ offsetof(MVarChar, data) +#define MVARCHARLENGTH(m) ( VARSIZE(m)-MVARCHARHDRSZ ) +#define UVARCHARLENGTH(m) ( MVARCHARLENGTH(m)/sizeof(UChar) ) + +#define DatumGetMVarChar(m) ((MVarChar*)DatumGetPointer(m)) +#define MVarCharGetDatum(m) PointerGetDatum(m) + +#define PG_GETARG_MVARCHAR(n) DatumGetMVarChar(PG_DETOAST_DATUM(PG_GETARG_DATUM(n))) +#define PG_GETARG_MVARCHAR_COPY(n) DatumGetMVarChar(PG_DETOAST_DATUM_COPY(PG_GETARG_DATUM(n))) + +#define PG_RETURN_MVARCHAR(m) PG_RETURN_POINTER(m) + + +int Char2UChar(const char * src, int srclen, UChar *dst); +int UChar2Char(const UChar * src, int srclen, char *dst); +int UChar2Wchar(UChar * src, int srclen, pg_wchar *dst); +int UCharCompare(UChar * a, int alen, UChar *b, int blen); +int UCharCaseCompare(UChar * a, int alen, UChar *b, int blen); + +void FillWhiteSpace( UChar *dst, int n ); + +int lengthWithoutSpaceVarChar(MVarChar *m); +int lengthWithoutSpaceChar(MChar *m); + +extern Datum mchar_hash(PG_FUNCTION_ARGS); +extern Datum mvarchar_hash(PG_FUNCTION_ARGS); + +int m_isspace(UChar c); /* is == ' ' */ + +Datum hash_uchar( UChar *s, int len ); +#endif diff --git a/contrib/mchar/mchar_io.c b/contrib/mchar/mchar_io.c new file mode 100644 index 00000000000..d6c2ac7d393 --- /dev/null +++ b/contrib/mchar/mchar_io.c @@ -0,0 +1,403 @@ +#include "mchar.h" +#include "mb/pg_wchar.h" +#include "fmgr.h" +#include "libpq/pqformat.h" +#include "nodes/nodeFuncs.h" +#include "nodes/supportnodes.h" +#include <utils/array.h> + +#ifdef PG_MODULE_MAGIC +PG_MODULE_MAGIC; +#endif + +PG_FUNCTION_INFO_V1(mchar_in); +Datum mchar_in(PG_FUNCTION_ARGS); +PG_FUNCTION_INFO_V1(mchar_out); +Datum mchar_out(PG_FUNCTION_ARGS); +PG_FUNCTION_INFO_V1(mchar); +Datum mchar(PG_FUNCTION_ARGS); + +PG_FUNCTION_INFO_V1(mvarchar_in); +Datum mvarchar_in(PG_FUNCTION_ARGS); +PG_FUNCTION_INFO_V1(mvarchar_out); +Datum mvarchar_out(PG_FUNCTION_ARGS); +PG_FUNCTION_INFO_V1(mvarchar); +Datum mvarchar(PG_FUNCTION_ARGS); + +PG_FUNCTION_INFO_V1(mvarchar_support); +Datum varchar_support(PG_FUNCTION_ARGS); + +PG_FUNCTION_INFO_V1(mchartypmod_in); +Datum mchartypmod_in(PG_FUNCTION_ARGS); +Datum +mchartypmod_in(PG_FUNCTION_ARGS) { + ArrayType *ta = PG_GETARG_ARRAYTYPE_P(0); + int32 *tl; + int n; + + tl = ArrayGetIntegerTypmods(ta, &n); + + if (n != 1) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid type modifier"))); + if (*tl < 1) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("length for type mchar/mvarchar must be at least 1"))); + + return *tl; +} + +PG_FUNCTION_INFO_V1(mchartypmod_out); +Datum mchartypmod_out(PG_FUNCTION_ARGS); +Datum +mchartypmod_out(PG_FUNCTION_ARGS) { + int32 typmod = PG_GETARG_INT32(0); + char *res = (char *) palloc(64); + + if (typmod >0) + snprintf(res, 64, "(%d)", (int) (typmod)); + else + *res = '\0'; + + PG_RETURN_CSTRING( res ); +} + +static void +mchar_strip( MChar * m, int atttypmod ) { + int maxlen; + + if ( atttypmod<=0 ) { + atttypmod =-1; + } else { + int charlen = u_countChar32( m->data, UCHARLENGTH(m) ); + + if ( charlen > atttypmod ) { + int i=0; + U16_FWD_N( m->data, i, UCHARLENGTH(m), atttypmod); + SET_VARSIZE( m, sizeof(UChar) * i + MCHARHDRSZ ); + } + } + + m->typmod = atttypmod; + + maxlen = UCHARLENGTH(m); + while( maxlen>0 && m_isspace( m->data[ maxlen-1 ] ) ) + maxlen--; + + SET_VARSIZE(m, sizeof(UChar) * maxlen + MCHARHDRSZ); +} + + +Datum +mchar_in(PG_FUNCTION_ARGS) { + char *s = PG_GETARG_CSTRING(0); +#ifdef NOT_USED + Oid typelem = PG_GETARG_OID(1); +#endif + int32 atttypmod = PG_GETARG_INT32(2); + MChar *result; + int32 slen = strlen(s), rlen; + + pg_verifymbstr(s, slen, false); + + result = (MChar*)palloc( MCHARHDRSZ + slen * sizeof(UChar) * 4 /* upper limit of length */ ); + rlen = Char2UChar( s, slen, result->data ); + SET_VARSIZE(result, sizeof(UChar) * rlen + MCHARHDRSZ); + + mchar_strip(result, atttypmod); + + PG_RETURN_MCHAR(result); +} + +Datum +mchar_out(PG_FUNCTION_ARGS) { + MChar *in = PG_GETARG_MCHAR(0); + char *out; + size_t size, inlen = UCHARLENGTH(in); + size_t charlen = u_countChar32(in->data, inlen); + + Assert( in->typmod < 0 || charlen<=in->typmod ); + size = ( in->typmod < 0 ) ? inlen : in->typmod; + size *= pg_database_encoding_max_length(); + + out = (char*)palloc( size+1 ); + size = UChar2Char( in->data, inlen, out ); + + if ( in->typmod>0 && charlen < in->typmod ) { + memset( out+size, ' ', in->typmod - charlen); + size += in->typmod - charlen; + } + + out[size] = '\0'; + + PG_FREE_IF_COPY(in,0); + + PG_RETURN_CSTRING(out); +} + +Datum +mchar(PG_FUNCTION_ARGS) { + MChar *source = PG_GETARG_MCHAR(0); + MChar *result; + int32 typmod = PG_GETARG_INT32(1); +#ifdef NOT_USED + bool isExplicit = PG_GETARG_BOOL(2); +#endif + + result = palloc( VARSIZE(source) ); + memcpy( result, source, VARSIZE(source) ); + PG_FREE_IF_COPY(source,0); + + mchar_strip(result, typmod); + + PG_RETURN_MCHAR(result); +} + +Datum +mvarchar_in(PG_FUNCTION_ARGS) { + char *s = PG_GETARG_CSTRING(0); +#ifdef NOT_USED + Oid typelem = PG_GETARG_OID(1); +#endif + int32 atttypmod = PG_GETARG_INT32(2); + MVarChar *result; + int32 slen = strlen(s), rlen; + + pg_verifymbstr(s, slen, false); + + result = (MVarChar*)palloc( MVARCHARHDRSZ + slen * sizeof(UChar) * 2 /* upper limit of length */ ); + rlen = Char2UChar( s, slen, result->data ); + SET_VARSIZE(result, sizeof(UChar) * rlen + MVARCHARHDRSZ); + + if ( atttypmod > 0 && atttypmod < u_countChar32(result->data, UVARCHARLENGTH(result)) ) + elog(ERROR,"value too long for type mvarchar(%d)", atttypmod); + + PG_RETURN_MVARCHAR(result); +} + +Datum +mvarchar_out(PG_FUNCTION_ARGS) { + MVarChar *in = PG_GETARG_MVARCHAR(0); + char *out; + size_t size = UVARCHARLENGTH(in); + + size *= pg_database_encoding_max_length(); + + out = (char*)palloc( size+1 ); + size = UChar2Char( in->data, UVARCHARLENGTH(in), out ); + + out[size] = '\0'; + + PG_FREE_IF_COPY(in,0); + + PG_RETURN_CSTRING(out); +} + +static void +mvarchar_strip(MVarChar *m, int atttypmod) { + int charlen = u_countChar32(m->data, UVARCHARLENGTH(m)); + + if ( atttypmod>=0 && atttypmod < charlen ) { + int i=0; + U16_FWD_N( m->data, i, charlen, atttypmod); + SET_VARSIZE(m, sizeof(UChar) * i + MVARCHARHDRSZ); + } +} + +Datum +mvarchar(PG_FUNCTION_ARGS) { + MVarChar *source = PG_GETARG_MVARCHAR(0); + MVarChar *result; + int32 typmod = PG_GETARG_INT32(1); + bool isExplicit = PG_GETARG_BOOL(2); + int charlen = u_countChar32(source->data, UVARCHARLENGTH(source)); + + result = palloc( VARSIZE(source) ); + memcpy( result, source, VARSIZE(source) ); + PG_FREE_IF_COPY(source,0); + + if ( typmod>=0 && typmod < charlen ) { + if ( isExplicit ) + mvarchar_strip(result, typmod); + else + elog(ERROR,"value too long for type mvarchar(%d)", typmod); + } + + PG_RETURN_MVARCHAR(result); +} + +PG_FUNCTION_INFO_V1(mvarchar_mchar); +Datum mvarchar_mchar(PG_FUNCTION_ARGS); +Datum +mvarchar_mchar(PG_FUNCTION_ARGS) { + MVarChar *source = PG_GETARG_MVARCHAR(0); + MChar *result; + int32 typmod = PG_GETARG_INT32(1); +#ifdef NOT_USED + bool isExplicit = PG_GETARG_BOOL(2); +#endif + + result = palloc( MVARCHARLENGTH(source) + MCHARHDRSZ ); + SET_VARSIZE(result, MVARCHARLENGTH(source) + MCHARHDRSZ); + memcpy( result->data, source->data, MVARCHARLENGTH(source)); + + PG_FREE_IF_COPY(source,0); + + mchar_strip( result, typmod ); + + PG_RETURN_MCHAR(result); +} + +PG_FUNCTION_INFO_V1(mchar_mvarchar); +Datum mchar_mvarchar(PG_FUNCTION_ARGS); +Datum +mchar_mvarchar(PG_FUNCTION_ARGS) { + MChar *source = PG_GETARG_MCHAR(0); + MVarChar *result; + int32 typmod = PG_GETARG_INT32(1); + int32 scharlen = u_countChar32(source->data, UCHARLENGTH(source)); + int32 curlen = 0, maxcharlen; +#ifdef NOT_USED + bool isExplicit = PG_GETARG_BOOL(2); +#endif + + maxcharlen = (source->typmod > 0) ? source->typmod : scharlen; + + result = palloc( MVARCHARHDRSZ + sizeof(UChar) * 2 * maxcharlen ); + + curlen = UCHARLENGTH( source ); + if ( curlen > 0 ) + memcpy( result->data, source->data, MCHARLENGTH(source) ); + if ( source->typmod > 0 && scharlen < source->typmod ) { + FillWhiteSpace( result->data + curlen, source->typmod-scharlen ); + curlen += source->typmod-scharlen; + } + SET_VARSIZE(result, MVARCHARHDRSZ + curlen *sizeof(UChar)); + + PG_FREE_IF_COPY(source,0); + + mvarchar_strip( result, typmod ); + + PG_RETURN_MCHAR(result); +} + +PG_FUNCTION_INFO_V1(mchar_send); +Datum mchar_send(PG_FUNCTION_ARGS); +Datum +mchar_send(PG_FUNCTION_ARGS) { + MChar *in = PG_GETARG_MCHAR(0); + size_t inlen = UCHARLENGTH(in); + size_t charlen = u_countChar32(in->data, inlen); + StringInfoData buf; + + Assert( in->typmod < 0 || charlen<=in->typmod ); + + pq_begintypsend(&buf); + pq_sendbytes(&buf, (char*)in->data, inlen * sizeof(UChar) ); + + if ( in->typmod>0 && charlen < in->typmod ) { + int nw = in->typmod - charlen; + UChar *white = palloc( sizeof(UChar) * nw ); + + FillWhiteSpace( white, nw ); + pq_sendbytes(&buf, (char*)white, sizeof(UChar) * nw); + pfree(white); + } + + PG_FREE_IF_COPY(in,0); + + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); +} + +PG_FUNCTION_INFO_V1(mchar_recv); +Datum mchar_recv(PG_FUNCTION_ARGS); +Datum +mchar_recv(PG_FUNCTION_ARGS) { + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + MChar *res; + int nbytes; +#ifdef NOT_USED + Oid typelem = PG_GETARG_OID(1); +#endif + int32 atttypmod = PG_GETARG_INT32(2); + + nbytes = buf->len - buf->cursor; + res = (MChar*)palloc( nbytes + MCHARHDRSZ ); + res->len = nbytes + MCHARHDRSZ; + res->typmod = -1; + SET_VARSIZE(res, res->len); + pq_copymsgbytes(buf, (char*)res->data, nbytes); + + mchar_strip( res, atttypmod ); + + PG_RETURN_MCHAR(res); +} + +PG_FUNCTION_INFO_V1(mvarchar_send); +Datum mvarchar_send(PG_FUNCTION_ARGS); +Datum +mvarchar_send(PG_FUNCTION_ARGS) { + MVarChar *in = PG_GETARG_MVARCHAR(0); + size_t inlen = UVARCHARLENGTH(in); + StringInfoData buf; + + pq_begintypsend(&buf); + pq_sendbytes(&buf, (char*)in->data, inlen * sizeof(UChar) ); + + PG_FREE_IF_COPY(in,0); + + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); +} + +PG_FUNCTION_INFO_V1(mvarchar_recv); +Datum mvarchar_recv(PG_FUNCTION_ARGS); +Datum +mvarchar_recv(PG_FUNCTION_ARGS) { + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + MVarChar *res; + int nbytes; +#ifdef NOT_USED + Oid typelem = PG_GETARG_OID(1); +#endif + int32 atttypmod = PG_GETARG_INT32(2); + + nbytes = buf->len - buf->cursor; + res = (MVarChar*)palloc( nbytes + MVARCHARHDRSZ ); + res->len = nbytes + MVARCHARHDRSZ; + SET_VARSIZE(res, res->len); + pq_copymsgbytes(buf, (char*)res->data, nbytes); + + mvarchar_strip( res, atttypmod ); + + PG_RETURN_MVARCHAR(res); +} + +Datum +mvarchar_support(PG_FUNCTION_ARGS) +{ + Node *node = (Node *) PG_GETARG_POINTER(0); + Node *ret = NULL; + + if (IsA(node, SupportRequestSimplify)) + { + SupportRequestSimplify *req = (SupportRequestSimplify *) node; + FuncExpr *expr = req->fcall; + Node *typmodnode; + + typmodnode = (Node *) lsecond(expr->args); + + if (IsA(typmodnode, Const) && !((Const *) typmodnode)->constisnull) + { + Node *source = (Node *) linitial(expr->args); + int32 source_typmod = exprTypmod(source); + int32 req_typemod = DatumGetInt32(((Const *) typmodnode)->constvalue); + + if (req_typemod < 0 || (source_typmod >= 0 && source_typmod <= req_typemod)) + ret = relabel_to_typmod(source, req_typemod); + } + } + + PG_RETURN_POINTER(ret); +} diff --git a/contrib/mchar/mchar_like.c b/contrib/mchar/mchar_like.c new file mode 100644 index 00000000000..1f83fb37864 --- /dev/null +++ b/contrib/mchar/mchar_like.c @@ -0,0 +1,929 @@ +#include "mchar.h" +#include "mb/pg_wchar.h" + +#include "catalog/pg_collation.h" +#include "utils/selfuncs.h" +#include "nodes/primnodes.h" +#include "nodes/makefuncs.h" +#include "nodes/supportnodes.h" +#include "regex/regex.h" + +/* +** Originally written by Rich $alz, mirror!rs, Wed Nov 26 19:03:17 EST 1986. +** Rich $alz is now <rsalz@bbn.com>. +** Special thanks to Lars Mathiesen <thorinn@diku.dk> for the LABORT code. +** +** This code was shamelessly stolen from the "pql" code by myself and +** slightly modified :) +** +** All references to the word "star" were replaced by "percent" +** All references to the word "wild" were replaced by "like" +** +** All the nice shell RE matching stuff was replaced by just "_" and "%" +** +** As I don't have a copy of the SQL standard handy I wasn't sure whether +** to leave in the '\' escape character handling. +** +** Keith Parks. <keith@mtcc.demon.co.uk> +** +** SQL92 lets you specify the escape character by saying +** LIKE <pattern> ESCAPE <escape character>. We are a small operation +** so we force you to use '\'. - ay 7/95 +** +** Now we have the like_escape() function that converts patterns with +** any specified escape character (or none at all) to the internal +** default escape character, which is still '\'. - tgl 9/2000 +** +** The code is rewritten to avoid requiring null-terminated strings, +** which in turn allows us to leave out some memcpy() operations. +** This code should be faster and take less memory, but no promises... +** - thomas 2000-08-06 +** +** Adopted for UTF-16 by teodor +*/ + +#define LIKE_TRUE 1 +#define LIKE_FALSE 0 +#define LIKE_ABORT (-1) + + +static int +uchareq(UChar *p1, UChar *p2) { + int l1=0, l2=0; + /* + * Count length of char: + * We suppose that string is correct!! + */ + U16_FWD_1(p1, l1, 2); + U16_FWD_1(p2, l2, 2); + + return (UCharCaseCompare(p1, l1, p2, l2)==0) ? 1 : 0; +} + +#define NextChar(p, plen) \ + do { \ + int __l = 0; \ + U16_FWD_1((p), __l, (plen));\ + (p) +=__l; \ + (plen) -=__l; \ + } while(0) + +#define CopyAdvChar(dst, src, srclen) \ + do { \ + int __l = 0; \ + U16_FWD_1((src), __l, (srclen));\ + (srclen) -= __l; \ + while (__l-- > 0) \ + *(dst)++ = *(src)++; \ + } while (0) + + +static UChar UCharPercent = 0; +static UChar UCharBackSlesh = 0; +static UChar UCharUnderLine = 0; +static UChar UCharStar = 0; +static UChar UCharDotDot = 0; +static UChar UCharUp = 0; +static UChar UCharLBracket = 0; +static UChar UCharQ = 0; +static UChar UCharRBracket = 0; +static UChar UCharDollar = 0; +static UChar UCharDot = 0; +static UChar UCharLFBracket = 0; +static UChar UCharRFBracket = 0; +static UChar UCharQuote = 0; +static UChar UCharSpace = 0; +static UChar UCharOne = 0; +static UChar UCharComma = 0; +static UChar UCharLQBracket = 0; +static UChar UCharRQBracket = 0; + +#define MkUChar(uc, c) do { \ + char __c = (c); \ + u_charsToUChars( &__c, &(uc), 1 ); \ +} while(0) + +#define SET_UCHAR if ( UCharPercent == 0 ) { \ + MkUChar( UCharPercent, '%' ); \ + MkUChar( UCharBackSlesh, '\\' ); \ + MkUChar( UCharUnderLine, '_' ); \ + MkUChar( UCharStar, '*' ); \ + MkUChar( UCharDotDot, ':' ); \ + MkUChar( UCharUp, '^' ); \ + MkUChar( UCharLBracket, '(' ); \ + MkUChar( UCharQ, '?' ); \ + MkUChar( UCharRBracket, ')' ); \ + MkUChar( UCharDollar, '$' ); \ + MkUChar( UCharDot, '.' ); \ + MkUChar( UCharLFBracket, '{' ); \ + MkUChar( UCharRFBracket, '}' ); \ + MkUChar( UCharQuote, '"' ); \ + MkUChar( UCharSpace, ' ' ); \ + MkUChar( UCharOne, '1' ); \ + MkUChar( UCharComma, ',' ); \ + MkUChar( UCharLQBracket, '[' ); \ + MkUChar( UCharRQBracket, ']' ); \ + } + +int +m_isspace(UChar c) { + SET_UCHAR; + + return (c == UCharSpace); +} + +static int +MatchUChar(UChar *t, int tlen, UChar *p, int plen) { + SET_UCHAR; + + /* Fast path for match-everything pattern */ + if ((plen == 1) && (*p == UCharPercent)) + return LIKE_TRUE; + + while ((tlen > 0) && (plen > 0)) { + if (*p == UCharBackSlesh) { + /* Next pattern char must match literally, whatever it is */ + NextChar(p, plen); + if ((plen <= 0) || !uchareq(t, p)) + return LIKE_FALSE; + } else if (*p == UCharPercent) { + /* %% is the same as % according to the SQL standard */ + /* Advance past all %'s */ + while ((plen > 0) && (*p == UCharPercent)) + NextChar(p, plen); + /* Trailing percent matches everything. */ + if (plen <= 0) + return LIKE_TRUE; + + /* + * Otherwise, scan for a text position at which we can match the + * rest of the pattern. + */ + while (tlen > 0) { + /* + * Optimization to prevent most recursion: don't recurse + * unless first pattern char might match this text char. + */ + if (uchareq(t, p) || (*p == UCharBackSlesh) || (*p == UCharUnderLine)) { + int matched = MatchUChar(t, tlen, p, plen); + + if (matched != LIKE_FALSE) + return matched; /* TRUE or ABORT */ + } + + NextChar(t, tlen); + } + + /* + * End of text with no match, so no point in trying later places + * to start matching this pattern. + */ + return LIKE_ABORT; + } if ((*p != UCharUnderLine) && !uchareq(t, p)) { + /* + * Not the single-character wildcard and no explicit match? Then + * time to quit... + */ + return LIKE_FALSE; + } + + NextChar(t, tlen); + NextChar(p, plen); + } + + if (tlen > 0) + return LIKE_FALSE; /* end of pattern, but not of text */ + + /* End of input string. Do we have matching pattern remaining? */ + while ((plen > 0) && (*p == UCharPercent)) /* allow multiple %'s at end of + * pattern */ + NextChar(p, plen); + if (plen <= 0) + return LIKE_TRUE; + + /* + * End of text with no match, so no point in trying later places to start + * matching this pattern. + */ + + return LIKE_ABORT; +} + +PG_FUNCTION_INFO_V1( mvarchar_like ); +Datum mvarchar_like( PG_FUNCTION_ARGS ); +Datum +mvarchar_like( PG_FUNCTION_ARGS ) { + MVarChar *str = PG_GETARG_MVARCHAR(0); + MVarChar *pat = PG_GETARG_MVARCHAR(1); + int result; + + result = MatchUChar( str->data, UVARCHARLENGTH(str), pat->data, UVARCHARLENGTH(pat) ); + + PG_FREE_IF_COPY(str,0); + PG_FREE_IF_COPY(pat,1); + + PG_RETURN_BOOL(result == LIKE_TRUE); +} + +PG_FUNCTION_INFO_V1( mvarchar_notlike ); +Datum mvarchar_notlike( PG_FUNCTION_ARGS ); +Datum +mvarchar_notlike( PG_FUNCTION_ARGS ) { + bool res = DatumGetBool( DirectFunctionCall2( + mvarchar_like, + PG_GETARG_DATUM(0), + PG_GETARG_DATUM(1) + )); + PG_RETURN_BOOL( !res ); +} + +/* + * Removes trailing spaces in '111 %' pattern + */ +static UChar * +removeTrailingSpaces( UChar *src, int srclen, int *dstlen, bool *isSpecialLast) { + UChar* dst = src; + UChar *ptr, *dptr, *markptr; + + *dstlen = srclen; + ptr = src + srclen-1; + SET_UCHAR; + + *isSpecialLast = ( srclen > 0 && (u_isspace(*ptr) || *ptr == UCharPercent || *ptr == UCharUnderLine ) ) ? true : false; + while( ptr>=src ) { + if ( *ptr == UCharPercent || *ptr == UCharUnderLine ) { + if ( ptr==src ) + return dst; /* first character */ + + if ( *(ptr-1) == UCharBackSlesh ) + return dst; /* use src as is */ + + if ( u_isspace( *(ptr-1) ) ) { + ptr--; + break; /* % or _ is after space which should be removed */ + } + } else { + return dst; + } + ptr--; + } + + markptr = ptr+1; + dst = (UChar*)palloc( sizeof(UChar) * srclen ); + + /* find last non-space character */ + while( ptr>=src && u_isspace(*ptr) ) + ptr--; + + dptr = dst + (ptr-src+1); + + if ( ptr>=src ) + memcpy( dst, src, sizeof(UChar) * (ptr-src+1) ); + + while( markptr - src < srclen ) { + *dptr = *markptr; + dptr++; + markptr++; + } + + *dstlen = dptr - dst; + return dst; +} + +static UChar* +addTrailingSpace( MChar *src, int *newlen ) { + int scharlen = u_countChar32(src->data, UCHARLENGTH(src)); + + if ( src->typmod > scharlen ) { + UChar *res = (UChar*) palloc( sizeof(UChar) * (UCHARLENGTH(src) + src->typmod) ); + + memcpy( res, src->data, sizeof(UChar) * UCHARLENGTH(src)); + FillWhiteSpace( res+UCHARLENGTH(src), src->typmod - scharlen ); + + *newlen = src->typmod; + + return res; + } else { + *newlen = UCHARLENGTH(src); + return src->data; + } +} + +PG_FUNCTION_INFO_V1( mchar_like ); +Datum mchar_like( PG_FUNCTION_ARGS ); +Datum +mchar_like( PG_FUNCTION_ARGS ) { + MChar *str = PG_GETARG_MCHAR(0); + MVarChar *pat = PG_GETARG_MVARCHAR(1); + int result; + bool isNeedAdd = false; + UChar *cleaned, *filled; + int clen=0, flen=0; + + cleaned = removeTrailingSpaces(pat->data, UVARCHARLENGTH(pat), &clen, &isNeedAdd); + if ( isNeedAdd ) + filled = addTrailingSpace(str, &flen); + else { + filled = str->data; + flen = UCHARLENGTH(str); + } + + result = MatchUChar( filled, flen, cleaned, clen ); + + if ( pat->data != cleaned ) + pfree( cleaned ); + if ( str->data != filled ) + pfree( filled ); + + PG_FREE_IF_COPY(str,0); + PG_FREE_IF_COPY(pat,1); + + PG_RETURN_BOOL(result == LIKE_TRUE); +} + +PG_FUNCTION_INFO_V1( mchar_notlike ); +Datum mchar_notlike( PG_FUNCTION_ARGS ); +Datum +mchar_notlike( PG_FUNCTION_ARGS ) { + bool res = DatumGetInt32( DirectFunctionCall2( + mchar_like, + PG_GETARG_DATUM(0), + PG_GETARG_DATUM(1) + )); + + PG_RETURN_BOOL( !res ); +} + + + +PG_FUNCTION_INFO_V1( mchar_pattern_fixed_prefix ); +Datum mchar_pattern_fixed_prefix( PG_FUNCTION_ARGS ); +Datum +mchar_pattern_fixed_prefix( PG_FUNCTION_ARGS ) { + Const *patt = (Const*)PG_GETARG_POINTER(0); + Pattern_Type ptype = (Pattern_Type)PG_GETARG_INT32(1); + Const **prefix = (Const**)PG_GETARG_POINTER(2); + UChar *spatt; + int32 slen, prefixlen=0, restlen=0, i=0; + MVarChar *sprefix; + MVarChar *srest; + Pattern_Prefix_Status status = Pattern_Prefix_None; + + *prefix = NULL; + + if ( ptype != Pattern_Type_Like ) + PG_RETURN_INT32(Pattern_Prefix_None); + + SET_UCHAR; + + spatt = ((MVarChar*)DatumGetPointer(patt->constvalue))->data; + slen = UVARCHARLENGTH( DatumGetPointer(patt->constvalue) ); + + sprefix = (MVarChar*)palloc( MCHARHDRSZ /*The biggest hdr!! */ + sizeof(UChar) * slen ); + srest = (MVarChar*)palloc( MCHARHDRSZ /*The biggest hdr!! */ + sizeof(UChar) * slen ); + + while( prefixlen < slen && i < slen ) { + if ( spatt[i] == UCharPercent || spatt[i] == UCharUnderLine ) + break; + else if ( spatt[i] == UCharBackSlesh ) { + i++; + if ( i>= slen ) + break; + } + sprefix->data[ prefixlen++ ] = spatt[i++]; + } + + while( prefixlen > 0 ) { + if ( ! u_isspace( sprefix->data[ prefixlen-1 ] ) ) + break; + prefixlen--; + } + + if ( prefixlen == 0 ) + PG_RETURN_INT32(Pattern_Prefix_None); + + for(;i<slen;i++) + srest->data[ restlen++ ] = spatt[i]; + + SET_VARSIZE(sprefix, sizeof(UChar) * prefixlen + MVARCHARHDRSZ); + SET_VARSIZE(srest, sizeof(UChar) * restlen + MVARCHARHDRSZ); + + *prefix = makeConst( patt->consttype, -1, InvalidOid, VARSIZE(sprefix), PointerGetDatum(sprefix), false, false ); + + if ( prefixlen == slen ) /* in LIKE, an empty pattern is an exact match! */ + status = Pattern_Prefix_Exact; + else if ( prefixlen > 0 ) + status = Pattern_Prefix_Partial; + + PG_RETURN_INT32( status ); +} + +static bool +checkCmp( UChar *left, int32 leftlen, UChar *right, int32 rightlen ) { + + return (UCharCaseCompare( left, leftlen, right, rightlen) < 0 ) ? true : false; +} + + +PG_FUNCTION_INFO_V1( mchar_greaterstring ); +Datum mchar_greaterstring( PG_FUNCTION_ARGS ); +Datum +mchar_greaterstring( PG_FUNCTION_ARGS ) { + Const *patt = (Const*)PG_GETARG_POINTER(0); + char *src = (char*)DatumGetPointer( patt->constvalue ); + int dstlen, srclen = VARSIZE(src); + char *dst = palloc( srclen ); + UChar *ptr, *srcptr; + + memcpy( dst, src, srclen ); + + srclen = dstlen = UVARCHARLENGTH( dst ); + ptr = ((MVarChar*)dst)->data; + srcptr = ((MVarChar*)src)->data; + + while( dstlen > 0 ) { + UChar *lastchar = ptr + dstlen - 1; + + if ( !U16_IS_LEAD( *lastchar ) ) { + while( *lastchar<0xffff ) { + + (*lastchar)++; + + if ( ublock_getCode(*lastchar) == UBLOCK_INVALID_CODE || !checkCmp( srcptr, srclen, ptr, dstlen ) ) + continue; + else { + SET_VARSIZE(dst, sizeof(UChar) * dstlen + MVARCHARHDRSZ); + + PG_RETURN_POINTER( makeConst( patt->consttype, -1, + InvalidOid, VARSIZE(dst), PointerGetDatum(dst), false, false ) ); + } + } + } + + dstlen--; + } + + PG_RETURN_POINTER(NULL); +} + +static int +do_like_escape( UChar *pat, int plen, UChar *esc, int elen, UChar *result) { + UChar *p = pat,*e =esc ,*r; + bool afterescape; + + r = result; + SET_UCHAR; + + if ( elen == 0 ) { + /* + * No escape character is wanted. Double any backslashes in the + * pattern to make them act like ordinary characters. + */ + while (plen > 0) { + if (*p == UCharBackSlesh ) + *r++ = UCharBackSlesh; + CopyAdvChar(r, p, plen); + } + } else { + /* + * The specified escape must be only a single character. + */ + NextChar(e, elen); + + if (elen != 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_ESCAPE_SEQUENCE), + errmsg("invalid escape string"), + errhint("Escape string must be empty or one character."))); + + e = esc; + + /* + * If specified escape is '\', just copy the pattern as-is. + */ + if ( *e == UCharBackSlesh ) { + memcpy(result, pat, plen * sizeof(UChar)); + return plen; + } + + /* + * Otherwise, convert occurrences of the specified escape character to + * '\', and double occurrences of '\' --- unless they immediately + * follow an escape character! + */ + afterescape = false; + + while (plen > 0) { + if ( uchareq(p,e) && !afterescape) { + *r++ = UCharBackSlesh; + NextChar(p, plen); + afterescape = true; + } else if ( *p == UCharBackSlesh ) { + *r++ = UCharBackSlesh; + if (!afterescape) + *r++ = UCharBackSlesh; + NextChar(p, plen); + afterescape = false; + } else { + CopyAdvChar(r, p, plen); + afterescape = false; + } + } + } + + return ( r - result ); +} + +PG_FUNCTION_INFO_V1( mvarchar_like_escape ); +Datum mvarchar_like_escape( PG_FUNCTION_ARGS ); +Datum +mvarchar_like_escape( PG_FUNCTION_ARGS ) { + MVarChar *pat = PG_GETARG_MVARCHAR(0); + MVarChar *esc = PG_GETARG_MVARCHAR(1); + MVarChar *result; + + result = (MVarChar*)palloc( MVARCHARHDRSZ + sizeof(UChar)*2*UVARCHARLENGTH(pat) ); + result->len = MVARCHARHDRSZ + do_like_escape( pat->data, UVARCHARLENGTH(pat), + esc->data, UVARCHARLENGTH(esc), + result->data ) * sizeof(UChar); + + SET_VARSIZE(result, result->len); + PG_FREE_IF_COPY(pat,0); + PG_FREE_IF_COPY(esc,1); + + PG_RETURN_MVARCHAR(result); +} + +#define RE_CACHE_SIZE 32 +typedef struct ReCache { + UChar *pattern; + int length; + int flags; + regex_t re; +} ReCache; + +static int num_res = 0; +static ReCache re_array[RE_CACHE_SIZE]; /* cached re's */ +static const int mchar_regex_flavor = REG_ADVANCED | REG_ICASE; + +static regex_t * +URE_compile_and_cache(UChar *text_re, int text_re_len, int cflags) { + pg_wchar *pattern; + size_t pattern_len; + int i; + int regcomp_result; + ReCache re_temp; + char errMsg[128]; + + + for (i = 0; i < num_res; i++) { + if ( re_array[i].length == text_re_len && + re_array[i].flags == cflags && + memcmp(re_array[i].pattern, text_re, sizeof(UChar)*text_re_len) == 0 ) { + + /* Found, move it to front */ + if ( i>0 ) { + re_temp = re_array[i]; + memmove(&re_array[1], &re_array[0], i * sizeof(ReCache)); + re_array[0] = re_temp; + } + + return &re_array[0].re; + } + } + + pattern = (pg_wchar *) palloc((1 + text_re_len) * sizeof(pg_wchar)); + pattern_len = UChar2Wchar(text_re, text_re_len, pattern); + + regcomp_result = pg_regcomp(&re_temp.re, + pattern, + pattern_len, + cflags, + DEFAULT_COLLATION_OID); + pfree( pattern ); + + if (regcomp_result != REG_OKAY) { + pg_regerror(regcomp_result, &re_temp.re, errMsg, sizeof(errMsg)); + ereport(ERROR, + (errcode(ERRCODE_INVALID_REGULAR_EXPRESSION), + errmsg("invalid regular expression: %s", errMsg))); + } + + re_temp.pattern = malloc( text_re_len*sizeof(UChar) ); + if ( re_temp.pattern == NULL ) + elog(ERROR,"Out of memory"); + + memcpy(re_temp.pattern, text_re, text_re_len*sizeof(UChar) ); + re_temp.length = text_re_len; + re_temp.flags = cflags; + + if (num_res >= RE_CACHE_SIZE) { + --num_res; + Assert(num_res < RE_CACHE_SIZE); + pg_regfree(&re_array[num_res].re); + free(re_array[num_res].pattern); + } + + if (num_res > 0) + memmove(&re_array[1], &re_array[0], num_res * sizeof(ReCache)); + + re_array[0] = re_temp; + num_res++; + + return &re_array[0].re; +} + +static bool +URE_compile_and_execute(UChar *pat, int pat_len, UChar *dat, int dat_len, + int cflags, int nmatch, regmatch_t *pmatch) { + pg_wchar *data; + size_t data_len; + int regexec_result; + regex_t *re; + char errMsg[128]; + + data = (pg_wchar *) palloc((1+dat_len) * sizeof(pg_wchar)); + data_len = UChar2Wchar(dat, dat_len, data); + + re = URE_compile_and_cache(pat, pat_len, cflags); + + regexec_result = pg_regexec(re, + data, + data_len, + 0, + NULL, + nmatch, + pmatch, + 0); + pfree(data); + + if (regexec_result != REG_OKAY && regexec_result != REG_NOMATCH) { + /* re failed??? */ + pg_regerror(regexec_result, re, errMsg, sizeof(errMsg)); + ereport(ERROR, + (errcode(ERRCODE_INVALID_REGULAR_EXPRESSION), + errmsg("regular expression failed: %s", errMsg))); + } + + return (regexec_result == REG_OKAY); +} + +PG_FUNCTION_INFO_V1( mchar_regexeq ); +Datum mchar_regexeq( PG_FUNCTION_ARGS ); +Datum +mchar_regexeq( PG_FUNCTION_ARGS ) { + MChar *t = PG_GETARG_MCHAR(0); + MChar *p = PG_GETARG_MCHAR(1); + bool res; + + res = URE_compile_and_execute(p->data, UCHARLENGTH(p), + t->data, UCHARLENGTH(t), + mchar_regex_flavor, + 0, NULL); + PG_FREE_IF_COPY(t, 0); + PG_FREE_IF_COPY(p, 1); + + PG_RETURN_BOOL(res); +} + +PG_FUNCTION_INFO_V1( mchar_regexne ); +Datum mchar_regexne( PG_FUNCTION_ARGS ); +Datum +mchar_regexne( PG_FUNCTION_ARGS ) { + MChar *t = PG_GETARG_MCHAR(0); + MChar *p = PG_GETARG_MCHAR(1); + bool res; + + res = URE_compile_and_execute(p->data, UCHARLENGTH(p), + t->data, UCHARLENGTH(t), + mchar_regex_flavor, + 0, NULL); + PG_FREE_IF_COPY(t, 0); + PG_FREE_IF_COPY(p, 1); + + PG_RETURN_BOOL(!res); +} + +PG_FUNCTION_INFO_V1( mvarchar_regexeq ); +Datum mvarchar_regexeq( PG_FUNCTION_ARGS ); +Datum +mvarchar_regexeq( PG_FUNCTION_ARGS ) { + MVarChar *t = PG_GETARG_MVARCHAR(0); + MVarChar *p = PG_GETARG_MVARCHAR(1); + bool res; + + res = URE_compile_and_execute(p->data, UVARCHARLENGTH(p), + t->data, UVARCHARLENGTH(t), + mchar_regex_flavor, + 0, NULL); + PG_FREE_IF_COPY(t, 0); + PG_FREE_IF_COPY(p, 1); + + PG_RETURN_BOOL(res); +} + +PG_FUNCTION_INFO_V1( mvarchar_regexne ); +Datum mvarchar_regexne( PG_FUNCTION_ARGS ); +Datum +mvarchar_regexne( PG_FUNCTION_ARGS ) { + MVarChar *t = PG_GETARG_MVARCHAR(0); + MVarChar *p = PG_GETARG_MVARCHAR(1); + bool res; + + res = URE_compile_and_execute(p->data, UVARCHARLENGTH(p), + t->data, UVARCHARLENGTH(t), + mchar_regex_flavor, + 0, NULL); + PG_FREE_IF_COPY(t, 0); + PG_FREE_IF_COPY(p, 1); + + PG_RETURN_BOOL(!res); +} + +static int +do_similar_escape(UChar *p, int plen, UChar *e, int elen, UChar *result) { + UChar *r; + bool afterescape = false; + bool incharclass = false; + int nquotes = 0; + + SET_UCHAR; + + if (e==NULL || elen <0 ) { + e = &UCharBackSlesh; + elen = 1; + } else { + if ( elen == 0 ) + e = NULL; + else if ( elen != 1) + ereport(ERROR, + (errcode(ERRCODE_INVALID_ESCAPE_SEQUENCE), + errmsg("invalid escape string"), + errhint("Escape string must be empty or one character."))); + } + + /* + * Look explanation of following in ./utils/adt/regexp.c + */ + r = result; + + *r++ = UCharUp; + *r++ = UCharLBracket; + *r++ = UCharQ; + *r++ = UCharDotDot; + + while( plen>0 ) { + UChar pchar = *p; + + if (afterescape) + { + if (pchar == UCharQuote && !incharclass) /* escape-double-quote? */ + { + if (nquotes == 0) + { + *r++ = UCharRBracket; + *r++ = UCharLFBracket; + *r++ = UCharOne; + *r++ = UCharComma; + *r++ = UCharOne; + *r++ = UCharRFBracket; + *r++ = UCharQ; + *r++ = UCharLBracket; + } + else if (nquotes == 1) + { + *r++ = UCharRBracket; + *r++ = UCharLFBracket; + *r++ = UCharOne; + *r++ = UCharComma; + *r++ = UCharOne; + *r++ = UCharRFBracket; + *r++ = UCharLBracket; + *r++ = UCharQ; + *r++ = UCharDotDot; + } + else + ereport(ERROR, + (errcode(ERRCODE_INVALID_USE_OF_ESCAPE_CHARACTER), + errmsg("SQL regular expression may not contain more than two escape-double-quote separators"))); + nquotes++; + } + else + { + *r++ = UCharBackSlesh; + *r++ = pchar; + } + afterescape = false; + } + else if (e && elen > 0 && pchar == *e) + { + afterescape = true; + } + else if (incharclass) + { + if (pchar == UCharBackSlesh) + *r++ = UCharBackSlesh; + *r++ = pchar; + if (pchar == UCharRQBracket) + incharclass = false; + } + else if (pchar == UCharLQBracket) + { + *r++ = pchar; + incharclass = true; + } + else if (pchar == UCharPercent) + { + *r++ = UCharDot; + *r++ = UCharStar; + } + else if (pchar == UCharUnderLine) + *r++ = UCharDot; + else if (pchar == UCharLBracket) + { + *r++ = UCharLBracket; + *r++ = UCharQ; + *r++ = UCharDotDot; + } + else if (pchar == UCharBackSlesh || pchar == UCharDot || + pchar == UCharUp || pchar == UCharDollar) + { + *r++ = UCharBackSlesh; + *r++ = pchar; + } + else + *r++ = pchar; + + p++, plen--; + } + + *r++ = UCharRBracket; + *r++ = UCharDollar; + + return r-result; +} + +PG_FUNCTION_INFO_V1( mchar_similar_escape ); +Datum mchar_similar_escape( PG_FUNCTION_ARGS ); +Datum +mchar_similar_escape( PG_FUNCTION_ARGS ) { + MChar *pat; + MChar *esc; + MChar *result; + + if (PG_ARGISNULL(0)) + PG_RETURN_NULL(); + pat = PG_GETARG_MCHAR(0); + + if (PG_NARGS() < 2 || PG_ARGISNULL(1)) { + esc = NULL; + } else { + esc = PG_GETARG_MCHAR(1); + } + + result = (MChar*)palloc( MCHARHDRSZ + sizeof(UChar)*(23 + 3*UCHARLENGTH(pat)) ); + result->len = MCHARHDRSZ + do_similar_escape( pat->data, UCHARLENGTH(pat), + (esc) ? esc->data : NULL, (esc) ? UCHARLENGTH(esc) : -1, + result->data ) * sizeof(UChar); + result->typmod=-1; + + SET_VARSIZE(result, result->len); + PG_FREE_IF_COPY(pat,0); + if ( esc ) + PG_FREE_IF_COPY(esc,1); + + PG_RETURN_MCHAR(result); +} + +PG_FUNCTION_INFO_V1( mvarchar_similar_escape ); +Datum mvarchar_similar_escape( PG_FUNCTION_ARGS ); +Datum +mvarchar_similar_escape( PG_FUNCTION_ARGS ) { + MVarChar *pat; + MVarChar *esc; + MVarChar *result; + + if (PG_ARGISNULL(0)) + PG_RETURN_NULL(); + pat = PG_GETARG_MVARCHAR(0); + + if (PG_NARGS() < 2 || PG_ARGISNULL(1)) { + esc = NULL; + } else { + esc = PG_GETARG_MVARCHAR(1); + } + + result = (MVarChar*)palloc( MVARCHARHDRSZ + sizeof(UChar)*(23 + 3*UVARCHARLENGTH(pat)) ); + result->len = MVARCHARHDRSZ + do_similar_escape( pat->data, UVARCHARLENGTH(pat), + (esc) ? esc->data : NULL, (esc) ? UVARCHARLENGTH(esc) : -1, + result->data ) * sizeof(UChar); + + SET_VARSIZE(result, result->len); + PG_FREE_IF_COPY(pat,0); + if ( esc ) + PG_FREE_IF_COPY(esc,1); + + PG_RETURN_MVARCHAR(result); +} + +#define RE_CACHE_SIZE 32 diff --git a/contrib/mchar/mchar_op.c b/contrib/mchar/mchar_op.c new file mode 100644 index 00000000000..4694d9cf3c3 --- /dev/null +++ b/contrib/mchar/mchar_op.c @@ -0,0 +1,449 @@ +#include "mchar.h" + +int +lengthWithoutSpaceVarChar(MVarChar *m) { + int l = UVARCHARLENGTH(m); + + while( l>0 && m_isspace( m->data[ l-1 ] ) ) + l--; + + return l; +} + +int +lengthWithoutSpaceChar(MChar *m) { + int l = UCHARLENGTH(m); + + while( l>0 && m_isspace( m->data[ l-1 ] ) ) + l--; + + return l; +} + +static inline int +mchar_icase_compare( MChar *a, MChar *b ) { + return UCharCaseCompare( + a->data, lengthWithoutSpaceChar(a), + b->data, lengthWithoutSpaceChar(b) + ); +} + +static inline int +mchar_case_compare( MChar *a, MChar *b ) { + return UCharCompare( + a->data, lengthWithoutSpaceChar(a), + b->data, lengthWithoutSpaceChar(b) + ); +} + +#define MCHARCMPFUNC( c, type, action, ret ) \ +PG_FUNCTION_INFO_V1( mchar_##c##_##type ); \ +Datum mchar_##c##_##type(PG_FUNCTION_ARGS);\ +Datum \ +mchar_##c##_##type(PG_FUNCTION_ARGS) { \ + MChar *a = PG_GETARG_MCHAR(0); \ + MChar *b = PG_GETARG_MCHAR(1); \ + int res = mchar_##c##_compare(a,b); \ + \ + PG_FREE_IF_COPY(a,0); \ + PG_FREE_IF_COPY(b,1); \ + PG_RETURN_##ret( res action 0 ); \ +} + + +MCHARCMPFUNC( case, eq, ==, BOOL ) +MCHARCMPFUNC( case, ne, !=, BOOL ) +MCHARCMPFUNC( case, lt, <, BOOL ) +MCHARCMPFUNC( case, le, <=, BOOL ) +MCHARCMPFUNC( case, ge, >=, BOOL ) +MCHARCMPFUNC( case, gt, >, BOOL ) +MCHARCMPFUNC( case, cmp, +, INT32 ) + +MCHARCMPFUNC( icase, eq, ==, BOOL ) +MCHARCMPFUNC( icase, ne, !=, BOOL ) +MCHARCMPFUNC( icase, lt, <, BOOL ) +MCHARCMPFUNC( icase, le, <=, BOOL ) +MCHARCMPFUNC( icase, ge, >=, BOOL ) +MCHARCMPFUNC( icase, gt, >, BOOL ) +MCHARCMPFUNC( icase, cmp, +, INT32 ) + +PG_FUNCTION_INFO_V1( mchar_larger ); +Datum mchar_larger( PG_FUNCTION_ARGS ); +Datum +mchar_larger( PG_FUNCTION_ARGS ) { + MChar *a = PG_GETARG_MCHAR(0); + MChar *b = PG_GETARG_MCHAR(1); + MChar *r; + + r = ( mchar_icase_compare(a,b) > 0 ) ? a : b; + + PG_RETURN_MCHAR(r); +} + +PG_FUNCTION_INFO_V1( mchar_smaller ); +Datum mchar_smaller( PG_FUNCTION_ARGS ); +Datum +mchar_smaller( PG_FUNCTION_ARGS ) { + MChar *a = PG_GETARG_MCHAR(0); + MChar *b = PG_GETARG_MCHAR(1); + MChar *r; + + r = ( mchar_icase_compare(a,b) < 0 ) ? a : b; + + PG_RETURN_MCHAR(r); +} + + +PG_FUNCTION_INFO_V1( mchar_concat ); +Datum mchar_concat( PG_FUNCTION_ARGS ); +Datum +mchar_concat( PG_FUNCTION_ARGS ) { + MChar *a = PG_GETARG_MCHAR(0); + MChar *b = PG_GETARG_MCHAR(1); + MChar *result; + int maxcharlen, curlen; + int acharlen = u_countChar32(a->data, UCHARLENGTH(a)), + bcharlen = u_countChar32(b->data, UCHARLENGTH(b)); + + + maxcharlen = ((a->typmod<=0) ? acharlen : a->typmod) + + ((b->typmod<=0) ? bcharlen : b->typmod); + + result = (MChar*)palloc( MCHARHDRSZ + sizeof(UChar) * 2 * maxcharlen ); + + curlen = UCHARLENGTH( a ); + if ( curlen > 0 ) + memcpy( result->data, a->data, MCHARLENGTH(a) ); + if ( a->typmod > 0 && acharlen < a->typmod ) { + FillWhiteSpace( result->data + curlen, a->typmod-acharlen ); + curlen += a->typmod-acharlen; + } + + if ( UCHARLENGTH(b) > 0 ) { + memcpy( result->data + curlen, b->data, MCHARLENGTH( b ) ); + curlen += UCHARLENGTH( b ); + } + if ( b->typmod > 0 && bcharlen < b->typmod ) { + FillWhiteSpace( result->data + curlen, b->typmod-bcharlen ); + curlen += b->typmod-bcharlen; + } + + + result->typmod = -1; + SET_VARSIZE(result, sizeof(UChar) * curlen + MCHARHDRSZ); + + PG_FREE_IF_COPY(a,0); + PG_FREE_IF_COPY(b,1); + + PG_RETURN_MCHAR(result); +} + +static inline int +mvarchar_icase_compare( MVarChar *a, MVarChar *b ) { + + return UCharCaseCompare( + a->data, lengthWithoutSpaceVarChar(a), + b->data, lengthWithoutSpaceVarChar(b) + ); +} + +static inline int +mvarchar_case_compare( MVarChar *a, MVarChar *b ) { + return UCharCompare( + a->data, lengthWithoutSpaceVarChar(a), + b->data, lengthWithoutSpaceVarChar(b) + ); +} + +#define MVARCHARCMPFUNC( c, type, action, ret ) \ +PG_FUNCTION_INFO_V1( mvarchar_##c##_##type ); \ +Datum mvarchar_##c##_##type(PG_FUNCTION_ARGS); \ +Datum \ +mvarchar_##c##_##type(PG_FUNCTION_ARGS) { \ + MVarChar *a = PG_GETARG_MVARCHAR(0); \ + MVarChar *b = PG_GETARG_MVARCHAR(1); \ + int res = mvarchar_##c##_compare(a,b); \ + \ + PG_FREE_IF_COPY(a,0); \ + PG_FREE_IF_COPY(b,1); \ + PG_RETURN_##ret( res action 0 ); \ +} + + +MVARCHARCMPFUNC( case, eq, ==, BOOL ) +MVARCHARCMPFUNC( case, ne, !=, BOOL ) +MVARCHARCMPFUNC( case, lt, <, BOOL ) +MVARCHARCMPFUNC( case, le, <=, BOOL ) +MVARCHARCMPFUNC( case, ge, >=, BOOL ) +MVARCHARCMPFUNC( case, gt, >, BOOL ) +MVARCHARCMPFUNC( case, cmp, +, INT32 ) + +MVARCHARCMPFUNC( icase, eq, ==, BOOL ) +MVARCHARCMPFUNC( icase, ne, !=, BOOL ) +MVARCHARCMPFUNC( icase, lt, <, BOOL ) +MVARCHARCMPFUNC( icase, le, <=, BOOL ) +MVARCHARCMPFUNC( icase, ge, >=, BOOL ) +MVARCHARCMPFUNC( icase, gt, >, BOOL ) +MVARCHARCMPFUNC( icase, cmp, +, INT32 ) + +PG_FUNCTION_INFO_V1( mvarchar_larger ); +Datum mvarchar_larger( PG_FUNCTION_ARGS ); +Datum +mvarchar_larger( PG_FUNCTION_ARGS ) { + MVarChar *a = PG_GETARG_MVARCHAR(0); + MVarChar *b = PG_GETARG_MVARCHAR(1); + MVarChar *r; + + r = ( mvarchar_icase_compare(a,b) > 0 ) ? a : b; + + PG_RETURN_MVARCHAR(r); +} + +PG_FUNCTION_INFO_V1( mvarchar_smaller ); +Datum mvarchar_smaller( PG_FUNCTION_ARGS ); +Datum +mvarchar_smaller( PG_FUNCTION_ARGS ) { + MVarChar *a = PG_GETARG_MVARCHAR(0); + MVarChar *b = PG_GETARG_MVARCHAR(1); + MVarChar *r; + + r = ( mvarchar_icase_compare(a,b) < 0 ) ? a : b; + + PG_RETURN_MVARCHAR(r); +} + +PG_FUNCTION_INFO_V1( mvarchar_concat ); +Datum mvarchar_concat( PG_FUNCTION_ARGS ); +Datum +mvarchar_concat( PG_FUNCTION_ARGS ) { + MVarChar *a = PG_GETARG_MVARCHAR(0); + MVarChar *b = PG_GETARG_MVARCHAR(1); + MVarChar *result; + int curlen; + int acharlen = u_countChar32(a->data, UVARCHARLENGTH(a)), + bcharlen = u_countChar32(b->data, UVARCHARLENGTH(b)); + + result = (MVarChar*)palloc( MVARCHARHDRSZ + sizeof(UChar) * 2 * (acharlen + bcharlen) ); + + curlen = UVARCHARLENGTH( a ); + if ( curlen > 0 ) + memcpy( result->data, a->data, MVARCHARLENGTH(a) ); + + if ( UVARCHARLENGTH(b) > 0 ) { + memcpy( result->data + curlen, b->data, MVARCHARLENGTH( b ) ); + curlen += UVARCHARLENGTH( b ); + } + + SET_VARSIZE(result, sizeof(UChar) * curlen + MVARCHARHDRSZ); + + PG_FREE_IF_COPY(a,0); + PG_FREE_IF_COPY(b,1); + + PG_RETURN_MVARCHAR(result); +} + +PG_FUNCTION_INFO_V1( mchar_mvarchar_concat ); +Datum mchar_mvarchar_concat( PG_FUNCTION_ARGS ); +Datum +mchar_mvarchar_concat( PG_FUNCTION_ARGS ) { + MChar *a = PG_GETARG_MCHAR(0); + MVarChar *b = PG_GETARG_MVARCHAR(1); + MVarChar *result; + int curlen, maxcharlen; + int acharlen = u_countChar32(a->data, UCHARLENGTH(a)), + bcharlen = u_countChar32(b->data, UVARCHARLENGTH(b)); + + maxcharlen = ((a->typmod<=0) ? acharlen : a->typmod) + bcharlen; + + result = (MVarChar*)palloc( MVARCHARHDRSZ + sizeof(UChar) * 2 * maxcharlen ); + + curlen = UCHARLENGTH( a ); + if ( curlen > 0 ) + memcpy( result->data, a->data, MCHARLENGTH(a) ); + if ( a->typmod > 0 && acharlen < a->typmod ) { + FillWhiteSpace( result->data + curlen, a->typmod-acharlen ); + curlen += a->typmod-acharlen; + } + + if ( UVARCHARLENGTH(b) > 0 ) { + memcpy( result->data + curlen, b->data, MVARCHARLENGTH( b ) ); + curlen += UVARCHARLENGTH( b ); + } + + SET_VARSIZE(result, sizeof(UChar) * curlen + MVARCHARHDRSZ); + + PG_FREE_IF_COPY(a,0); + PG_FREE_IF_COPY(b,1); + + PG_RETURN_MVARCHAR(result); +} + +PG_FUNCTION_INFO_V1( mvarchar_mchar_concat ); +Datum mvarchar_mchar_concat( PG_FUNCTION_ARGS ); +Datum +mvarchar_mchar_concat( PG_FUNCTION_ARGS ) { + MVarChar *a = PG_GETARG_MVARCHAR(0); + MChar *b = PG_GETARG_MCHAR(1); + MVarChar *result; + int curlen, maxcharlen; + int acharlen = u_countChar32(a->data, UVARCHARLENGTH(a)), + bcharlen = u_countChar32(b->data, UCHARLENGTH(b)); + + maxcharlen = acharlen + ((b->typmod<=0) ? bcharlen : b->typmod); + + result = (MVarChar*)palloc( MVARCHARHDRSZ + sizeof(UChar) * 2 * maxcharlen ); + + curlen = UVARCHARLENGTH( a ); + if ( curlen > 0 ) + memcpy( result->data, a->data, MVARCHARLENGTH(a) ); + + if ( UCHARLENGTH(b) > 0 ) { + memcpy( result->data + curlen, b->data, MCHARLENGTH( b ) ); + curlen += UCHARLENGTH( b ); + } + if ( b->typmod > 0 && bcharlen < b->typmod ) { + FillWhiteSpace( result->data + curlen, b->typmod-bcharlen ); + curlen += b->typmod-bcharlen; + } + + SET_VARSIZE(result, sizeof(UChar) * curlen + MVARCHARHDRSZ); + + PG_FREE_IF_COPY(a,0); + PG_FREE_IF_COPY(b,1); + + PG_RETURN_MVARCHAR(result); +} + +/* + * mchar <> mvarchar + */ +static inline int +mc_mv_icase_compare( MChar *a, MVarChar *b ) { + return UCharCaseCompare( + a->data, lengthWithoutSpaceChar(a), + b->data, lengthWithoutSpaceVarChar(b) + ); +} + +static inline int +mc_mv_case_compare( MChar *a, MVarChar *b ) { + return UCharCompare( + a->data, lengthWithoutSpaceChar(a), + b->data, lengthWithoutSpaceVarChar(b) + ); +} + +#define MC_MV_CHARCMPFUNC( c, type, action, ret ) \ +PG_FUNCTION_INFO_V1( mc_mv_##c##_##type ); \ +Datum mc_mv_##c##_##type(PG_FUNCTION_ARGS);\ +Datum \ +mc_mv_##c##_##type(PG_FUNCTION_ARGS) { \ + MChar *a = PG_GETARG_MCHAR(0); \ + MVarChar *b = PG_GETARG_MVARCHAR(1); \ + int res = mc_mv_##c##_compare(a,b); \ + \ + PG_FREE_IF_COPY(a,0); \ + PG_FREE_IF_COPY(b,1); \ + PG_RETURN_##ret( res action 0 ); \ +} + + +MC_MV_CHARCMPFUNC( case, eq, ==, BOOL ) +MC_MV_CHARCMPFUNC( case, ne, !=, BOOL ) +MC_MV_CHARCMPFUNC( case, lt, <, BOOL ) +MC_MV_CHARCMPFUNC( case, le, <=, BOOL ) +MC_MV_CHARCMPFUNC( case, ge, >=, BOOL ) +MC_MV_CHARCMPFUNC( case, gt, >, BOOL ) +MC_MV_CHARCMPFUNC( case, cmp, +, INT32 ) + +MC_MV_CHARCMPFUNC( icase, eq, ==, BOOL ) +MC_MV_CHARCMPFUNC( icase, ne, !=, BOOL ) +MC_MV_CHARCMPFUNC( icase, lt, <, BOOL ) +MC_MV_CHARCMPFUNC( icase, le, <=, BOOL ) +MC_MV_CHARCMPFUNC( icase, ge, >=, BOOL ) +MC_MV_CHARCMPFUNC( icase, gt, >, BOOL ) +MC_MV_CHARCMPFUNC( icase, cmp, +, INT32 ) + +/* + * mvarchar <> mchar + */ +static inline int +mv_mc_icase_compare( MVarChar *a, MChar *b ) { + return UCharCaseCompare( + a->data, lengthWithoutSpaceVarChar(a), + b->data, lengthWithoutSpaceChar(b) + ); +} + +static inline int +mv_mc_case_compare( MVarChar *a, MChar *b ) { + return UCharCompare( + a->data, lengthWithoutSpaceVarChar(a), + b->data, lengthWithoutSpaceChar(b) + ); +} + +#define MV_MC_CHARCMPFUNC( c, type, action, ret ) \ +PG_FUNCTION_INFO_V1( mv_mc_##c##_##type ); \ +Datum mv_mc_##c##_##type(PG_FUNCTION_ARGS);\ +Datum \ +mv_mc_##c##_##type(PG_FUNCTION_ARGS) { \ + MVarChar *a = PG_GETARG_MVARCHAR(0); \ + MChar *b = PG_GETARG_MCHAR(1); \ + int res = mv_mc_##c##_compare(a,b); \ + \ + PG_FREE_IF_COPY(a,0); \ + PG_FREE_IF_COPY(b,1); \ + PG_RETURN_##ret( res action 0 ); \ +} + + +MV_MC_CHARCMPFUNC( case, eq, ==, BOOL ) +MV_MC_CHARCMPFUNC( case, ne, !=, BOOL ) +MV_MC_CHARCMPFUNC( case, lt, <, BOOL ) +MV_MC_CHARCMPFUNC( case, le, <=, BOOL ) +MV_MC_CHARCMPFUNC( case, ge, >=, BOOL ) +MV_MC_CHARCMPFUNC( case, gt, >, BOOL ) +MV_MC_CHARCMPFUNC( case, cmp, +, INT32 ) + +MV_MC_CHARCMPFUNC( icase, eq, ==, BOOL ) +MV_MC_CHARCMPFUNC( icase, ne, !=, BOOL ) +MV_MC_CHARCMPFUNC( icase, lt, <, BOOL ) +MV_MC_CHARCMPFUNC( icase, le, <=, BOOL ) +MV_MC_CHARCMPFUNC( icase, ge, >=, BOOL ) +MV_MC_CHARCMPFUNC( icase, gt, >, BOOL ) +MV_MC_CHARCMPFUNC( icase, cmp, +, INT32 ) + +#define NULLHASHVALUE (-2147483647) + +#define FULLEQ_FUNC(type, cmpfunc, hashfunc) \ +PG_FUNCTION_INFO_V1( isfulleq_##type ); \ +Datum isfulleq_##type(PG_FUNCTION_ARGS); \ +Datum \ +isfulleq_##type(PG_FUNCTION_ARGS) { \ + if ( PG_ARGISNULL(0) && PG_ARGISNULL(1) ) \ + PG_RETURN_BOOL(true); \ + else if ( PG_ARGISNULL(0) || PG_ARGISNULL(1) ) \ + PG_RETURN_BOOL(false); \ + \ + PG_RETURN_DATUM( DirectFunctionCall2( cmpfunc, \ + PG_GETARG_DATUM(0), \ + PG_GETARG_DATUM(1) \ + ) ); \ +} \ + \ +PG_FUNCTION_INFO_V1( fullhash_##type ); \ +Datum fullhash_##type(PG_FUNCTION_ARGS); \ +Datum \ +fullhash_##type(PG_FUNCTION_ARGS) { \ + if ( PG_ARGISNULL(0) ) \ + PG_RETURN_INT32(NULLHASHVALUE); \ + \ + PG_RETURN_DATUM( DirectFunctionCall1( hashfunc, \ + PG_GETARG_DATUM(0) \ + ) ); \ +} + +FULLEQ_FUNC( mchar, mchar_icase_eq, mchar_hash ); +FULLEQ_FUNC( mvarchar, mvarchar_icase_eq, mvarchar_hash ); + diff --git a/contrib/mchar/mchar_proc.c b/contrib/mchar/mchar_proc.c new file mode 100644 index 00000000000..edabfb5eb66 --- /dev/null +++ b/contrib/mchar/mchar_proc.c @@ -0,0 +1,315 @@ +#include "mchar.h" +#include "mb/pg_wchar.h" + +PG_FUNCTION_INFO_V1(mchar_length); +Datum mchar_length(PG_FUNCTION_ARGS); + +Datum +mchar_length(PG_FUNCTION_ARGS) { + MChar *m = PG_GETARG_MCHAR(0); + int32 l = UCHARLENGTH(m); + + while( l>0 && m_isspace( m->data[ l-1 ] ) ) + l--; + + l = u_countChar32(m->data, l); + + PG_FREE_IF_COPY(m,0); + + PG_RETURN_INT32(l); +} + +PG_FUNCTION_INFO_V1(mvarchar_length); +Datum mvarchar_length(PG_FUNCTION_ARGS); + +Datum +mvarchar_length(PG_FUNCTION_ARGS) { + MVarChar *m = PG_GETARG_MVARCHAR(0); + int32 l = UVARCHARLENGTH(m); + + while( l>0 && m_isspace( m->data[ l-1 ] ) ) + l--; + + l = u_countChar32(m->data, l); + + PG_FREE_IF_COPY(m,0); + + PG_RETURN_INT32(l); +} + +static int32 +uchar_substring( + UChar *str, int32 strl, + int32 start, int32 length, bool length_not_specified, + UChar *dst) { + int32 S = start-1; /* start position */ + int32 S1; /* adjusted start position */ + int32 L1; /* adjusted substring length */ + int32 subbegin=0, subend=0; + + S1 = Max(S, 0); + if (length_not_specified) + L1 = -1; + else { + /* end position */ + int32 E = S + length; + + /* + * A negative value for L is the only way for the end position to + * be before the start. SQL99 says to throw an error. + */ + + if (E < S) + ereport(ERROR, + (errcode(ERRCODE_SUBSTRING_ERROR), + errmsg("negative substring length not allowed"))); + + /* + * A zero or negative value for the end position can happen if the + * start was negative or one. SQL99 says to return a zero-length + * string. + */ + if (E < 0) + return 0; + + L1 = E - S1; + } + + U16_FWD_N( str, subbegin, strl, S1 ); + if ( subbegin >= strl ) + return 0; + subend = subbegin; + U16_FWD_N( str, subend, strl, L1 ); + + memcpy( dst, str+subbegin, sizeof(UChar)*(subend-subbegin) ); + + return subend-subbegin; +} + +PG_FUNCTION_INFO_V1(mchar_substring); +Datum mchar_substring(PG_FUNCTION_ARGS); +Datum +mchar_substring(PG_FUNCTION_ARGS) { + MChar *src = PG_GETARG_MCHAR(0); + MChar *dst; + int32 length; + + dst = (MChar*)palloc( VARSIZE(src) ); + length = uchar_substring( + src->data, UCHARLENGTH(src), + PG_GETARG_INT32(1), PG_GETARG_INT32(2), false, + dst->data); + + dst->typmod = src->typmod; + SET_VARSIZE(dst, MCHARHDRSZ + length *sizeof(UChar)); + + PG_FREE_IF_COPY(src, 0); + PG_RETURN_MCHAR(dst); +} + +PG_FUNCTION_INFO_V1(mchar_substring_no_len); +Datum mchar_substring_no_len(PG_FUNCTION_ARGS); +Datum +mchar_substring_no_len(PG_FUNCTION_ARGS) { + MChar *src = PG_GETARG_MCHAR(0); + MChar *dst; + int32 length; + + dst = (MChar*)palloc( VARSIZE(src) ); + length = uchar_substring( + src->data, UCHARLENGTH(src), + PG_GETARG_INT32(1), -1, true, + dst->data); + + dst->typmod = src->typmod; + SET_VARSIZE(dst, MCHARHDRSZ + length *sizeof(UChar)); + + PG_FREE_IF_COPY(src, 0); + PG_RETURN_MCHAR(dst); +} + +PG_FUNCTION_INFO_V1(mvarchar_substring); +Datum mvarchar_substring(PG_FUNCTION_ARGS); +Datum +mvarchar_substring(PG_FUNCTION_ARGS) { + MVarChar *src = PG_GETARG_MVARCHAR(0); + MVarChar *dst; + int32 length; + + dst = (MVarChar*)palloc( VARSIZE(src) ); + length = uchar_substring( + src->data, UVARCHARLENGTH(src), + PG_GETARG_INT32(1), PG_GETARG_INT32(2), false, + dst->data); + + SET_VARSIZE(dst, MVARCHARHDRSZ + length *sizeof(UChar)); + + PG_FREE_IF_COPY(src, 0); + PG_RETURN_MVARCHAR(dst); +} + +PG_FUNCTION_INFO_V1(mvarchar_substring_no_len); +Datum mvarchar_substring_no_len(PG_FUNCTION_ARGS); +Datum +mvarchar_substring_no_len(PG_FUNCTION_ARGS) { + MVarChar *src = PG_GETARG_MVARCHAR(0); + MVarChar *dst; + int32 length; + + dst = (MVarChar*)palloc( VARSIZE(src) ); + length = uchar_substring( + src->data, UVARCHARLENGTH(src), + PG_GETARG_INT32(1), -1, true, + dst->data); + + SET_VARSIZE(dst, MVARCHARHDRSZ + length *sizeof(UChar)); + + PG_FREE_IF_COPY(src, 0); + PG_RETURN_MVARCHAR(dst); +} + +PG_FUNCTION_INFO_V1(mvarchar_hash); +Datum +mvarchar_hash(PG_FUNCTION_ARGS) { + MVarChar *src = PG_GETARG_MVARCHAR(0); + Datum res; + + res = hash_uchar( src->data, lengthWithoutSpaceVarChar(src) ); + + PG_FREE_IF_COPY(src, 0); + PG_RETURN_DATUM( res ); +} + +PG_FUNCTION_INFO_V1(mchar_hash); +Datum +mchar_hash(PG_FUNCTION_ARGS) { + MChar *src = PG_GETARG_MCHAR(0); + Datum res; + + res = hash_uchar( src->data, lengthWithoutSpaceChar(src) ); + + PG_FREE_IF_COPY(src, 0); + PG_RETURN_DATUM( res ); +} + +PG_FUNCTION_INFO_V1(mchar_upper); +Datum mchar_upper(PG_FUNCTION_ARGS); +Datum +mchar_upper(PG_FUNCTION_ARGS) { + MChar *src = PG_GETARG_MCHAR(0); + MChar *dst = (MChar*)palloc( VARSIZE(src) * 2 ); + + dst->len = MCHARHDRSZ; + dst->typmod = src->typmod; + if ( UCHARLENGTH(src) != 0 ) { + int length; + UErrorCode err=0; + + length = u_strToUpper( dst->data, VARSIZE(src) * 2 - MCHARHDRSZ, + src->data, UCHARLENGTH(src), + NULL, &err ); + + Assert( length <= VARSIZE(src) * 2 - MCHARHDRSZ ); + + if ( U_FAILURE(err) ) + elog(ERROR,"ICU u_strToUpper fails and returns %d (%s)", err, u_errorName(err)); + + dst->len += sizeof(UChar) * length; + } + + SET_VARSIZE( dst, dst->len ); + PG_FREE_IF_COPY(src, 0); + PG_RETURN_MCHAR( dst ); +} + +PG_FUNCTION_INFO_V1(mchar_lower); +Datum mchar_lower(PG_FUNCTION_ARGS); +Datum +mchar_lower(PG_FUNCTION_ARGS) { + MChar *src = PG_GETARG_MCHAR(0); + MChar *dst = (MChar*)palloc( VARSIZE(src) * 2 ); + + dst->len = MCHARHDRSZ; + dst->typmod = src->typmod; + if ( UCHARLENGTH(src) != 0 ) { + int length; + UErrorCode err=0; + + length = u_strToLower( dst->data, VARSIZE(src) * 2 - MCHARHDRSZ, + src->data, UCHARLENGTH(src), + NULL, &err ); + + Assert( length <= VARSIZE(src) * 2 - MCHARHDRSZ ); + + if ( U_FAILURE(err) ) + elog(ERROR,"ICU u_strToLower fails and returns %d (%s)", err, u_errorName(err)); + + dst->len += sizeof(UChar) * length; + } + + SET_VARSIZE( dst, dst->len ); + PG_FREE_IF_COPY(src, 0); + PG_RETURN_MCHAR( dst ); +} + +PG_FUNCTION_INFO_V1(mvarchar_upper); +Datum mvarchar_upper(PG_FUNCTION_ARGS); +Datum +mvarchar_upper(PG_FUNCTION_ARGS) { + MVarChar *src = PG_GETARG_MVARCHAR(0); + MVarChar *dst = (MVarChar*)palloc( VARSIZE(src) * 2 ); + + dst->len = MVARCHARHDRSZ; + + if ( UVARCHARLENGTH(src) != 0 ) { + int length; + UErrorCode err=0; + + length = u_strToUpper( dst->data, VARSIZE(src) * 2 - MVARCHARHDRSZ, + src->data, UVARCHARLENGTH(src), + NULL, &err ); + + Assert( length <= VARSIZE(src) * 2 - MVARCHARHDRSZ ); + + if ( U_FAILURE(err) ) + elog(ERROR,"ICU u_strToUpper fails and returns %d (%s)", err, u_errorName(err)); + + dst->len += sizeof(UChar) * length; + } + + SET_VARSIZE( dst, dst->len ); + PG_FREE_IF_COPY(src, 0); + PG_RETURN_MVARCHAR( dst ); +} + +PG_FUNCTION_INFO_V1(mvarchar_lower); +Datum mvarchar_lower(PG_FUNCTION_ARGS); +Datum +mvarchar_lower(PG_FUNCTION_ARGS) { + MVarChar *src = PG_GETARG_MVARCHAR(0); + MVarChar *dst = (MVarChar*)palloc( VARSIZE(src) * 2 ); + + dst->len = MVARCHARHDRSZ; + + if ( UVARCHARLENGTH(src) != 0 ) { + int length; + UErrorCode err=0; + + length = u_strToLower( dst->data, VARSIZE(src) * 2 - MVARCHARHDRSZ, + src->data, UVARCHARLENGTH(src), + NULL, &err ); + + Assert( length <= VARSIZE(src) * 2 - MVARCHARHDRSZ ); + + if ( U_FAILURE(err) ) + elog(ERROR,"ICU u_strToLower fails and returns %d (%s)", err, u_errorName(err)); + + dst->len += sizeof(UChar) * length; + } + + SET_VARSIZE( dst, dst->len ); + PG_FREE_IF_COPY(src, 0); + PG_RETURN_MVARCHAR( dst ); +} + + diff --git a/contrib/mchar/mchar_recode.c b/contrib/mchar/mchar_recode.c new file mode 100644 index 00000000000..12bc6d4f3aa --- /dev/null +++ b/contrib/mchar/mchar_recode.c @@ -0,0 +1,166 @@ +#include "mchar.h" +#include "access/hash.h" + +#include "unicode/ucol.h" +#include "unicode/ucnv.h" + +static UConverter *cnvDB = NULL; +static UCollator *colCaseInsensitive = NULL; +static UCollator *colCaseSensitive = NULL; + +static void +createUObjs() { + if ( !cnvDB ) { + UErrorCode err = 0; + + if ( GetDatabaseEncoding() == PG_UTF8 ) + cnvDB = ucnv_open("UTF8", &err); + else + cnvDB = ucnv_open(NULL, &err); + if ( U_FAILURE(err) || cnvDB == NULL ) + elog(ERROR,"ICU ucnv_open returns %d (%s)", err, u_errorName(err)); + } + + if ( !colCaseInsensitive ) { + UErrorCode err = 0; + + colCaseInsensitive = ucol_open("", &err); + if ( U_FAILURE(err) || cnvDB == NULL ) { + if ( colCaseSensitive ) + ucol_close( colCaseSensitive ); + colCaseSensitive = NULL; + elog(ERROR,"ICU ucol_open returns %d (%s)", err, u_errorName(err)); + } + + ucol_setStrength( colCaseInsensitive, UCOL_SECONDARY ); + } + + if ( !colCaseSensitive ) { + UErrorCode err = 0; + + colCaseSensitive = ucol_open("", &err); + if ( U_FAILURE(err) || cnvDB == NULL ) { + if ( colCaseSensitive ) + ucol_close( colCaseSensitive ); + colCaseSensitive = NULL; + elog(ERROR,"ICU ucol_open returns %d (%s)", err, u_errorName(err)); + } + + ucol_setAttribute(colCaseSensitive, UCOL_CASE_FIRST, UCOL_UPPER_FIRST, &err); + if (U_FAILURE(err)) { + if ( colCaseSensitive ) + ucol_close( colCaseSensitive ); + colCaseSensitive = NULL; + elog(ERROR,"ICU ucol_setAttribute returns %d (%s)", err, u_errorName(err)); + } + } +} + +int +Char2UChar(const char * src, int srclen, UChar *dst) { + int dstlen=0; + UErrorCode err = 0; + + createUObjs(); + dstlen = ucnv_toUChars( cnvDB, dst, srclen*4, src, srclen, &err ); + if ( U_FAILURE(err)) + elog(ERROR,"ICU ucnv_toUChars returns %d (%s)", err, u_errorName(err)); + + return dstlen; +} + +int +UChar2Char(const UChar * src, int srclen, char *dst) { + int dstlen=0; + UErrorCode err = 0; + + createUObjs(); + dstlen = ucnv_fromUChars( cnvDB, dst, srclen*4, src, srclen, &err ); + if ( U_FAILURE(err) ) + elog(ERROR,"ICU ucnv_fromUChars returns %d (%s)", err, u_errorName(err)); + + return dstlen; +} + +int +UChar2Wchar(UChar * src, int srclen, pg_wchar *dst) { + int dstlen=0; + char *utf = palloc(sizeof(char)*srclen*4); + + dstlen = UChar2Char(src, srclen, utf); + dstlen = pg_mb2wchar_with_len( utf, dst, dstlen ); + pfree(utf); + + return dstlen; +} + +static UChar UCharWhiteSpace = 0; + +void +FillWhiteSpace( UChar *dst, int n ) { + if ( UCharWhiteSpace == 0 ) { + int len; + UErrorCode err = 0; + + u_strFromUTF8( &UCharWhiteSpace, 1, &len, " ", 1, &err); + + Assert( len==1 ); + Assert( !U_FAILURE(err) ); + } + + while( n-- > 0 ) + *dst++ = UCharWhiteSpace; +} + +int +UCharCaseCompare(UChar * a, int alen, UChar *b, int blen) { + + createUObjs(); + + return (int)ucol_strcoll( colCaseInsensitive, + a, alen, + b, blen); +} + +int +UCharCompare(UChar * a, int alen, UChar *b, int blen) { + + createUObjs(); + + return (int)ucol_strcoll( colCaseSensitive, + a, alen, + b, blen); +} + +Datum +hash_uchar( UChar *s, int len ) { + int32 length = INT_MAX, i; + Datum res; + uint8 *d; + + if ( len == 0 ) + return hash_any( NULL, 0 ); + + createUObjs(); + + for(i=2;; i*=2) + { + d = palloc(len * i); + length = ucol_getSortKey(colCaseInsensitive, s, len, d, len*i); + + if (length == 0) + elog(ERROR,"ICU ucol_getSortKey fails"); + + if (length < len*i) + break; + + pfree(d); + } + + res = hash_any( (unsigned char*) d, length); + + pfree(d); + + return res; +} + diff --git a/contrib/mchar/sql/compat.sql b/contrib/mchar/sql/compat.sql new file mode 100644 index 00000000000..d5b6a986960 --- /dev/null +++ b/contrib/mchar/sql/compat.sql @@ -0,0 +1,11 @@ +--- table based checks + +select '<' || ch || '>', '<' || vch || '>' from chvch; +select * from chvch where vch = 'One space'; +select * from chvch where vch = 'One space '; + +select * from ch where chcol = 'abcd' order by chcol; +select * from ch t1 join ch t2 on t1.chcol = t2.chcol order by t1.chcol, t2.chcol; +select * from ch where chcol > 'abcd' and chcol<'ee'; +select * from ch order by chcol; + diff --git a/contrib/mchar/sql/init.sql b/contrib/mchar/sql/init.sql new file mode 100644 index 00000000000..04310044458 --- /dev/null +++ b/contrib/mchar/sql/init.sql @@ -0,0 +1,23 @@ +CREATE EXTENSION mchar; + +create table ch ( + chcol mchar(32) +) without oids; + +insert into ch values('abcd'); +insert into ch values('AbcD'); +insert into ch values('abcz'); +insert into ch values('defg'); +insert into ch values('dEfg'); +insert into ch values('ee'); +insert into ch values('Ee'); + +create table chvch ( + ch mchar(12), + vch mvarchar(12) +) without oids; + +insert into chvch values('No spaces', 'No spaces'); +insert into chvch values('One space ', 'One space '); +insert into chvch values('1 space', '1 space '); + diff --git a/contrib/mchar/sql/like.sql b/contrib/mchar/sql/like.sql new file mode 100644 index 00000000000..c29cf4eb6f9 --- /dev/null +++ b/contrib/mchar/sql/like.sql @@ -0,0 +1,231 @@ +-- simplest examples +-- E061-04 like predicate +set standard_conforming_strings=off; + +SELECT 'hawkeye'::mchar LIKE 'h%' AS "true"; +SELECT 'hawkeye'::mchar NOT LIKE 'h%' AS "false"; + +SELECT 'hawkeye'::mchar LIKE 'H%' AS "true"; +SELECT 'hawkeye'::mchar NOT LIKE 'H%' AS "false"; + +SELECT 'hawkeye'::mchar LIKE 'indio%' AS "false"; +SELECT 'hawkeye'::mchar NOT LIKE 'indio%' AS "true"; + +SELECT 'hawkeye'::mchar LIKE 'h%eye' AS "true"; +SELECT 'hawkeye'::mchar NOT LIKE 'h%eye' AS "false"; + +SELECT 'indio'::mchar LIKE '_ndio' AS "true"; +SELECT 'indio'::mchar NOT LIKE '_ndio' AS "false"; + +SELECT 'indio'::mchar LIKE 'in__o' AS "true"; +SELECT 'indio'::mchar NOT LIKE 'in__o' AS "false"; + +SELECT 'indio'::mchar LIKE 'in_o' AS "false"; +SELECT 'indio'::mchar NOT LIKE 'in_o' AS "true"; + +SELECT 'hawkeye'::mvarchar LIKE 'h%' AS "true"; +SELECT 'hawkeye'::mvarchar NOT LIKE 'h%' AS "false"; + +SELECT 'hawkeye'::mvarchar LIKE 'H%' AS "true"; +SELECT 'hawkeye'::mvarchar NOT LIKE 'H%' AS "false"; + +SELECT 'hawkeye'::mvarchar LIKE 'indio%' AS "false"; +SELECT 'hawkeye'::mvarchar NOT LIKE 'indio%' AS "true"; + +SELECT 'hawkeye'::mvarchar LIKE 'h%eye' AS "true"; +SELECT 'hawkeye'::mvarchar NOT LIKE 'h%eye' AS "false"; + +SELECT 'indio'::mvarchar LIKE '_ndio' AS "true"; +SELECT 'indio'::mvarchar NOT LIKE '_ndio' AS "false"; + +SELECT 'indio'::mvarchar LIKE 'in__o' AS "true"; +SELECT 'indio'::mvarchar NOT LIKE 'in__o' AS "false"; + +SELECT 'indio'::mvarchar LIKE 'in_o' AS "false"; +SELECT 'indio'::mvarchar NOT LIKE 'in_o' AS "true"; + +-- unused escape character +SELECT 'hawkeye'::mchar LIKE 'h%'::mchar ESCAPE '#' AS "true"; +SELECT 'hawkeye'::mchar NOT LIKE 'h%'::mchar ESCAPE '#' AS "false"; + +SELECT 'indio'::mchar LIKE 'ind_o'::mchar ESCAPE '$' AS "true"; +SELECT 'indio'::mchar NOT LIKE 'ind_o'::mchar ESCAPE '$' AS "false"; + +-- escape character +-- E061-05 like predicate with escape clause +SELECT 'h%'::mchar LIKE 'h#%'::mchar ESCAPE '#' AS "true"; +SELECT 'h%'::mchar NOT LIKE 'h#%'::mchar ESCAPE '#' AS "false"; + +SELECT 'h%wkeye'::mchar LIKE 'h#%'::mchar ESCAPE '#' AS "false"; +SELECT 'h%wkeye'::mchar NOT LIKE 'h#%'::mchar ESCAPE '#' AS "true"; + +SELECT 'h%wkeye'::mchar LIKE 'h#%%'::mchar ESCAPE '#' AS "true"; +SELECT 'h%wkeye'::mchar NOT LIKE 'h#%%'::mchar ESCAPE '#' AS "false"; + +SELECT 'h%awkeye'::mchar LIKE 'h#%a%k%e'::mchar ESCAPE '#' AS "true"; +SELECT 'h%awkeye'::mchar NOT LIKE 'h#%a%k%e'::mchar ESCAPE '#' AS "false"; + +SELECT 'indio'::mchar LIKE '_ndio'::mchar ESCAPE '$' AS "true"; +SELECT 'indio'::mchar NOT LIKE '_ndio'::mchar ESCAPE '$' AS "false"; + +SELECT 'i_dio'::mchar LIKE 'i$_d_o'::mchar ESCAPE '$' AS "true"; +SELECT 'i_dio'::mchar NOT LIKE 'i$_d_o'::mchar ESCAPE '$' AS "false"; + +SELECT 'i_dio'::mchar LIKE 'i$_nd_o'::mchar ESCAPE '$' AS "false"; +SELECT 'i_dio'::mchar NOT LIKE 'i$_nd_o'::mchar ESCAPE '$' AS "true"; + +SELECT 'i_dio'::mchar LIKE 'i$_d%o'::mchar ESCAPE '$' AS "true"; +SELECT 'i_dio'::mchar NOT LIKE 'i$_d%o'::mchar ESCAPE '$' AS "false"; + +-- escape character same as pattern character +SELECT 'maca'::mchar LIKE 'm%aca' ESCAPE '%'::mchar AS "true"; +SELECT 'maca'::mchar NOT LIKE 'm%aca' ESCAPE '%'::mchar AS "false"; + +SELECT 'ma%a'::mchar LIKE 'm%a%%a' ESCAPE '%'::mchar AS "true"; +SELECT 'ma%a'::mchar NOT LIKE 'm%a%%a' ESCAPE '%'::mchar AS "false"; + +SELECT 'bear'::mchar LIKE 'b_ear' ESCAPE '_'::mchar AS "true"; +SELECT 'bear'::mchar NOT LIKE 'b_ear'::mchar ESCAPE '_' AS "false"; + +SELECT 'be_r'::mchar LIKE 'b_e__r' ESCAPE '_'::mchar AS "true"; +SELECT 'be_r'::mchar NOT LIKE 'b_e__r' ESCAPE '_'::mchar AS "false"; + +SELECT 'be_r'::mchar LIKE '__e__r' ESCAPE '_'::mchar AS "false"; +SELECT 'be_r'::mchar NOT LIKE '__e__r'::mchar ESCAPE '_' AS "true"; + +-- unused escape character +SELECT 'hawkeye'::mvarchar LIKE 'h%'::mvarchar ESCAPE '#' AS "true"; +SELECT 'hawkeye'::mvarchar NOT LIKE 'h%'::mvarchar ESCAPE '#' AS "false"; + +SELECT 'indio'::mvarchar LIKE 'ind_o'::mvarchar ESCAPE '$' AS "true"; +SELECT 'indio'::mvarchar NOT LIKE 'ind_o'::mvarchar ESCAPE '$' AS "false"; + +-- escape character +-- E061-05 like predicate with escape clause +SELECT 'h%'::mvarchar LIKE 'h#%'::mvarchar ESCAPE '#' AS "true"; +SELECT 'h%'::mvarchar NOT LIKE 'h#%'::mvarchar ESCAPE '#' AS "false"; + +SELECT 'h%wkeye'::mvarchar LIKE 'h#%'::mvarchar ESCAPE '#' AS "false"; +SELECT 'h%wkeye'::mvarchar NOT LIKE 'h#%'::mvarchar ESCAPE '#' AS "true"; + +SELECT 'h%wkeye'::mvarchar LIKE 'h#%%'::mvarchar ESCAPE '#' AS "true"; +SELECT 'h%wkeye'::mvarchar NOT LIKE 'h#%%'::mvarchar ESCAPE '#' AS "false"; + +SELECT 'h%awkeye'::mvarchar LIKE 'h#%a%k%e'::mvarchar ESCAPE '#' AS "true"; +SELECT 'h%awkeye'::mvarchar NOT LIKE 'h#%a%k%e'::mvarchar ESCAPE '#' AS "false"; + +SELECT 'indio'::mvarchar LIKE '_ndio'::mvarchar ESCAPE '$' AS "true"; +SELECT 'indio'::mvarchar NOT LIKE '_ndio'::mvarchar ESCAPE '$' AS "false"; + +SELECT 'i_dio'::mvarchar LIKE 'i$_d_o'::mvarchar ESCAPE '$' AS "true"; +SELECT 'i_dio'::mvarchar NOT LIKE 'i$_d_o'::mvarchar ESCAPE '$' AS "false"; + +SELECT 'i_dio'::mvarchar LIKE 'i$_nd_o'::mvarchar ESCAPE '$' AS "false"; +SELECT 'i_dio'::mvarchar NOT LIKE 'i$_nd_o'::mvarchar ESCAPE '$' AS "true"; + +SELECT 'i_dio'::mvarchar LIKE 'i$_d%o'::mvarchar ESCAPE '$' AS "true"; +SELECT 'i_dio'::mvarchar NOT LIKE 'i$_d%o'::mvarchar ESCAPE '$' AS "false"; + +-- escape character same as pattern character +SELECT 'maca'::mvarchar LIKE 'm%aca' ESCAPE '%'::mvarchar AS "true"; +SELECT 'maca'::mvarchar NOT LIKE 'm%aca' ESCAPE '%'::mvarchar AS "false"; + +SELECT 'ma%a'::mvarchar LIKE 'm%a%%a' ESCAPE '%'::mvarchar AS "true"; +SELECT 'ma%a'::mvarchar NOT LIKE 'm%a%%a' ESCAPE '%'::mvarchar AS "false"; + +SELECT 'bear'::mvarchar LIKE 'b_ear' ESCAPE '_'::mvarchar AS "true"; +SELECT 'bear'::mvarchar NOT LIKE 'b_ear'::mvarchar ESCAPE '_' AS "false"; + +SELECT 'be_r'::mvarchar LIKE 'b_e__r' ESCAPE '_'::mvarchar AS "true"; +SELECT 'be_r'::mvarchar NOT LIKE 'b_e__r' ESCAPE '_'::mvarchar AS "false"; + +SELECT 'be_r'::mvarchar LIKE '__e__r' ESCAPE '_'::mvarchar AS "false"; +SELECT 'be_r'::mvarchar NOT LIKE '__e__r'::mvarchar ESCAPE '_' AS "true"; + +-- similar to + +SELECT 'abc'::mchar SIMILAR TO 'abc'::mchar AS "true"; +SELECT 'abc'::mchar SIMILAR TO 'a'::mchar AS "false"; +SELECT 'abc'::mchar SIMILAR TO '%(b|d)%'::mchar AS "true"; +SELECT 'abc'::mchar SIMILAR TO '(b|c)%'::mchar AS "false"; +SELECT 'h%'::mchar SIMILAR TO 'h#%'::mchar AS "false"; +SELECT 'h%'::mchar SIMILAR TO 'h#%'::mchar ESCAPE '#' AS "true"; + +SELECT 'abc'::mvarchar SIMILAR TO 'abc'::mvarchar AS "true"; +SELECT 'abc'::mvarchar SIMILAR TO 'a'::mvarchar AS "false"; +SELECT 'abc'::mvarchar SIMILAR TO '%(b|d)%'::mvarchar AS "true"; +SELECT 'abc'::mvarchar SIMILAR TO '(b|c)%'::mvarchar AS "false"; +SELECT 'h%'::mvarchar SIMILAR TO 'h#%'::mvarchar AS "false"; +SELECT 'h%'::mvarchar SIMILAR TO 'h#%'::mvarchar ESCAPE '#' AS "true"; + +-- index support + +SELECT * from ch where chcol like 'aB_d' order by chcol using &<; +SELECT * from ch where chcol like 'aB%d' order by chcol using &<; +SELECT * from ch where chcol like 'aB%' order by chcol using &<; +SELECT * from ch where chcol like '%BC%' order by chcol using &<; +set enable_seqscan = off; +explain (costs off) +SELECT * from ch where chcol like 'aB_d' order by chcol using &<; +SELECT * from ch where chcol like 'aB_d' order by chcol using &<; +SELECT * from ch where chcol like 'aB%d' order by chcol using &<; +SELECT * from ch where chcol like 'aB%' order by chcol using &<; +SELECT * from ch where chcol like '%BC%' order by chcol using &<; +set enable_seqscan = on; + + +create table testt (f1 mchar(10)); +insert into testt values ('Abc-000001'); +insert into testt values ('Abc-000002'); +insert into testt values ('0000000001'); +insert into testt values ('0000000002'); + +select f1 from testt where f1::mvarchar like E'Abc\\-%'::mvarchar; +select * from testt where f1::mchar like E'Abc\\-%'::mchar; +create index testindex on testt(f1); +set enable_seqscan=off; +explain (costs off) +select f1 from testt where f1::mvarchar like E'Abc\\-%'::mvarchar; +select f1 from testt where f1::mvarchar like E'Abc\\-%'::mvarchar; +select * from testt where f1::mchar like E'Abc\\-%'::mchar; +set enable_seqscan = on; +drop table testt; + +create table testt (f1 mvarchar(10)); +insert into testt values ('Abc-000001'); +insert into testt values ('Abc-000002'); +insert into testt values ('0000000001'); +insert into testt values ('0000000002'); + +select f1 from testt where f1::mvarchar like E'Abc\\-%'::mvarchar; +select * from testt where f1::mchar like E'Abc\\-%'::mchar; +select * from testt where f1::mchar like E'Abc\\- %'::mchar; +select * from testt where f1::mchar like E' %'::mchar; +create index testindex on testt(f1); +set enable_seqscan=off; +explain (costs off) +select f1 from testt where f1::mvarchar like E'Abc\\-%'::mvarchar; +select f1 from testt where f1::mvarchar like E'Abc\\-%'::mvarchar; +select * from testt where f1::mchar like E'Abc\\-%'::mchar; +select * from testt where f1::mchar like E'Abc\\- %'::mchar; +select * from testt where f1::mchar like E' %'::mchar; +set enable_seqscan = on; +drop table testt; + + +CREATE TABLE test ( code mchar(5) NOT NULL ); +insert into test values('1111 '); +insert into test values('111 '); +insert into test values('11 '); +insert into test values('1 '); + +SELECT * FROM test WHERE code LIKE ('% '); + +set escape_string_warning = off; +SELECT CASE WHEN ('_'::text SIMILAR TO '[\\_]'::text ESCAPE '\\'::text) THEN TRUE ELSE FALSE END ; +SELECT CASE WHEN ('_'::mchar SIMILAR TO '[\\_]'::mchar ESCAPE '\\'::mchar) THEN TRUE ELSE FALSE END ; +SELECT CASE WHEN ('_'::mvarchar SIMILAR TO '[\\_]'::mvarchar ESCAPE '\\'::mvarchar) THEN TRUE ELSE FALSE END ; +reset escape_string_warning; +reset standard_conforming_strings; + + diff --git a/contrib/mchar/sql/mchar.sql b/contrib/mchar/sql/mchar.sql new file mode 100644 index 00000000000..2ec0e659f4f --- /dev/null +++ b/contrib/mchar/sql/mchar.sql @@ -0,0 +1,90 @@ +-- I/O tests + +select '1'::mchar; +select '2 '::mchar; +select '10 '::mchar; + +select '1'::mchar(2); +select '2 '::mchar(2); +select '3 '::mchar(2); +select '10 '::mchar(2); + +select ' '::mchar(10); +select ' '::mchar; + +-- operations & functions + +select length('1'::mchar); +select length('2 '::mchar); +select length('10 '::mchar); + +select length('1'::mchar(2)); +select length('2 '::mchar(2)); +select length('3 '::mchar(2)); +select length('10 '::mchar(2)); + +select length(' '::mchar(10)); +select length(' '::mchar); + +select 'asd'::mchar(10) || '>'::mchar(10); +select length('asd'::mchar(10) || '>'::mchar(10)); +select 'asd'::mchar(2) || '>'::mchar(10); +select length('asd'::mchar(2) || '>'::mchar(10)); + +-- Comparisons + +select 'asdf'::mchar = 'aSdf'::mchar; +select 'asdf'::mchar = 'aSdf '::mchar; +select 'asdf'::mchar = 'aSdf 1'::mchar(4); +select 'asdf'::mchar = 'aSdf 1'::mchar(5); +select 'asdf'::mchar = 'aSdf 1'::mchar(6); +select 'asdf'::mchar(3) = 'aSdf 1'::mchar(5); +select 'asdf'::mchar(3) = 'aSdf 1'::mchar(3); + +select 'asdf'::mchar < 'aSdf'::mchar; +select 'asdf'::mchar < 'aSdf '::mchar; +select 'asdf'::mchar < 'aSdf 1'::mchar(4); +select 'asdf'::mchar < 'aSdf 1'::mchar(5); +select 'asdf'::mchar < 'aSdf 1'::mchar(6); + +select 'asdf'::mchar <= 'aSdf'::mchar; +select 'asdf'::mchar <= 'aSdf '::mchar; +select 'asdf'::mchar <= 'aSdf 1'::mchar(4); +select 'asdf'::mchar <= 'aSdf 1'::mchar(5); +select 'asdf'::mchar <= 'aSdf 1'::mchar(6); + +select 'asdf'::mchar >= 'aSdf'::mchar; +select 'asdf'::mchar >= 'aSdf '::mchar; +select 'asdf'::mchar >= 'aSdf 1'::mchar(4); +select 'asdf'::mchar >= 'aSdf 1'::mchar(5); +select 'asdf'::mchar >= 'aSdf 1'::mchar(6); + +select 'asdf'::mchar > 'aSdf'::mchar; +select 'asdf'::mchar > 'aSdf '::mchar; +select 'asdf'::mchar > 'aSdf 1'::mchar(4); +select 'asdf'::mchar > 'aSdf 1'::mchar(5); +select 'asdf'::mchar > 'aSdf 1'::mchar(6); + +select max(ch) from chvch; +select min(ch) from chvch; + +select substr('1234567890'::mchar, 3) = '34567890' as "34567890"; +select substr('1234567890'::mchar, 4, 3) = '456' as "456"; + +select lower('asdfASDF'::mchar); +select upper('asdfASDF'::mchar); + +select 'asd'::mchar == 'aSd'::mchar; +select 'asd'::mchar == 'aCd'::mchar; +select 'asd'::mchar == NULL; +select NULL == 'aCd'::mchar; +select NULL::mchar == NULL; + + +--Note: here we use different space symbols, be carefull to copy it! +select v, count(*) from +(values (1, '4 242'::mchar), (2, '4 242'), (3, 'aSDF'), (4, 'asdf')) as t(i,v) group by v; +set enable_hashagg=off; +select v, count(*) from +(values (1, '4 242'::mchar), (2, '4 242'), (3, 'aSDF'), (4, 'asdf')) as t(i,v) group by v; +reset enable_hashagg; diff --git a/contrib/mchar/sql/mm.sql b/contrib/mchar/sql/mm.sql new file mode 100644 index 00000000000..2e11b937040 --- /dev/null +++ b/contrib/mchar/sql/mm.sql @@ -0,0 +1,196 @@ +select 'asd'::mchar::mvarchar; +select 'asd '::mchar::mvarchar; +select 'asd'::mchar(2)::mvarchar; +select 'asd '::mchar(2)::mvarchar; +select 'asd'::mchar(5)::mvarchar; +select 'asd '::mchar(5)::mvarchar; +select 'asd'::mchar::mvarchar(2); +select 'asd '::mchar::mvarchar(2); +select 'asd'::mchar(2)::mvarchar(2); +select 'asd '::mchar(2)::mvarchar(2); +select 'asd'::mchar(5)::mvarchar(2); +select 'asd '::mchar(5)::mvarchar(2); +select 'asd'::mchar::mvarchar(5); +select 'asd '::mchar::mvarchar(5); +select 'asd'::mchar(2)::mvarchar(5); +select 'asd '::mchar(2)::mvarchar(5); +select 'asd'::mchar(5)::mvarchar(5); +select 'asd '::mchar(5)::mvarchar(5); + +select 'asd'::mvarchar::mchar; +select 'asd '::mvarchar::mchar; +select 'asd'::mvarchar(2)::mchar; +select 'asd '::mvarchar(2)::mchar; +select 'asd'::mvarchar(5)::mchar; +select 'asd '::mvarchar(5)::mchar; +select 'asd'::mvarchar::mchar(2); +select 'asd '::mvarchar::mchar(2); +select 'asd'::mvarchar(2)::mchar(2); +select 'asd '::mvarchar(2)::mchar(2); +select 'asd'::mvarchar(5)::mchar(2); +select 'asd '::mvarchar(5)::mchar(2); +select 'asd'::mvarchar::mchar(5); +select 'asd '::mvarchar::mchar(5); +select 'asd'::mvarchar(2)::mchar(5); +select 'asd '::mvarchar(2)::mchar(5); +select 'asd'::mvarchar(5)::mchar(5); +select 'asd '::mvarchar(5)::mchar(5); + +select 'asd'::mchar || '123'; +select 'asd'::mchar || '123'::mchar; +select 'asd'::mchar || '123'::mvarchar; + +select 'asd '::mchar || '123'; +select 'asd '::mchar || '123'::mchar; +select 'asd '::mchar || '123'::mvarchar; + +select 'asd '::mchar || '123 '; +select 'asd '::mchar || '123 '::mchar; +select 'asd '::mchar || '123 '::mvarchar; + + +select 'asd'::mvarchar || '123'; +select 'asd'::mvarchar || '123'::mchar; +select 'asd'::mvarchar || '123'::mvarchar; + +select 'asd '::mvarchar || '123'; +select 'asd '::mvarchar || '123'::mchar; +select 'asd '::mvarchar || '123'::mvarchar; + +select 'asd '::mvarchar || '123 '; +select 'asd '::mvarchar || '123 '::mchar; +select 'asd '::mvarchar || '123 '::mvarchar; + + +select 'asd'::mchar(2) || '123'; +select 'asd'::mchar(2) || '123'::mchar; +select 'asd'::mchar(2) || '123'::mvarchar; + + +select 'asd '::mchar(2) || '123'; +select 'asd '::mchar(2) || '123'::mchar; +select 'asd '::mchar(2) || '123'::mvarchar; + + +select 'asd '::mchar(2) || '123 '; +select 'asd '::mchar(2) || '123 '::mchar; +select 'asd '::mchar(2) || '123 '::mvarchar; + +select 'asd'::mvarchar(2) || '123'; +select 'asd'::mvarchar(2) || '123'::mchar; +select 'asd'::mvarchar(2) || '123'::mvarchar; + +select 'asd '::mvarchar(2) || '123'; +select 'asd '::mvarchar(2) || '123'::mchar; +select 'asd '::mvarchar(2) || '123'::mvarchar; + +select 'asd '::mvarchar(2) || '123 '; +select 'asd '::mvarchar(2) || '123 '::mchar; +select 'asd '::mvarchar(2) || '123 '::mvarchar; + +select 'asd'::mchar(4) || '143'; +select 'asd'::mchar(4) || '123'::mchar; +select 'asd'::mchar(4) || '123'::mvarchar; + +select 'asd '::mchar(4) || '123'; +select 'asd '::mchar(4) || '123'::mchar; +select 'asd '::mchar(4) || '123'::mvarchar; + +select 'asd '::mchar(4) || '123 '; +select 'asd '::mchar(4) || '123 '::mchar; +select 'asd '::mchar(4) || '123 '::mvarchar; + +select 'asd'::mvarchar(4) || '123'; +select 'asd'::mvarchar(4) || '123'::mchar; +select 'asd'::mvarchar(4) || '123'::mvarchar; + +select 'asd '::mvarchar(4) || '123'; +select 'asd '::mvarchar(4) || '123'::mchar; +select 'asd '::mvarchar(4) || '123'::mvarchar; + +select 'asd '::mvarchar(4) || '123 '; +select 'asd '::mvarchar(4) || '123 '::mchar; +select 'asd '::mvarchar(4) || '123 '::mvarchar; + + +select 'asd '::mvarchar(4) || '123 '::mchar(4); +select 'asd '::mvarchar(4) || '123 '::mvarchar(4); +select 'asd '::mvarchar(4) || '123'::mchar(4); +select 'asd '::mvarchar(4) || '123'::mvarchar(4); + + +select 1 where 'f'::mchar='F'::mvarchar; +select 1 where 'f'::mchar='F '::mvarchar; +select 1 where 'f '::mchar='F'::mvarchar; +select 1 where 'f '::mchar='F '::mvarchar; + +select 1 where 'f'::mchar='F'::mvarchar(2); +select 1 where 'f'::mchar='F '::mvarchar(2); +select 1 where 'f '::mchar='F'::mvarchar(2); +select 1 where 'f '::mchar='F '::mvarchar(2); + +select 1 where 'f'::mchar(2)='F'::mvarchar; +select 1 where 'f'::mchar(2)='F '::mvarchar; +select 1 where 'f '::mchar(2)='F'::mvarchar; +select 1 where 'f '::mchar(2)='F '::mvarchar; + +select 1 where 'f'::mchar(2)='F'::mvarchar(2); +select 1 where 'f'::mchar(2)='F '::mvarchar(2); +select 1 where 'f '::mchar(2)='F'::mvarchar(2); +select 1 where 'f '::mchar(2)='F '::mvarchar(2); + +select 1 where 'foo'::mchar='FOO'::mvarchar; +select 1 where 'foo'::mchar='FOO '::mvarchar; +select 1 where 'foo '::mchar='FOO'::mvarchar; +select 1 where 'foo '::mchar='FOO '::mvarchar; + +select 1 where 'foo'::mchar='FOO'::mvarchar(2); +select 1 where 'foo'::mchar='FOO '::mvarchar(2); +select 1 where 'foo '::mchar='FOO'::mvarchar(2); +select 1 where 'foo '::mchar='FOO '::mvarchar(2); + +select 1 where 'foo'::mchar(2)='FOO'::mvarchar; +select 1 where 'foo'::mchar(2)='FOO '::mvarchar; +select 1 where 'foo '::mchar(2)='FOO'::mvarchar; +select 1 where 'foo '::mchar(2)='FOO '::mvarchar; + +select 1 where 'foo'::mchar(2)='FOO'::mvarchar(2); +select 1 where 'foo'::mchar(2)='FOO '::mvarchar(2); +select 1 where 'foo '::mchar(2)='FOO'::mvarchar(2); +select 1 where 'foo '::mchar(2)='FOO '::mvarchar(2); + +Select 'f'::mchar(1) Union Select 'o'::mvarchar(1); +Select 'f'::mvarchar(1) Union Select 'o'::mchar(1); + +select * from chvch where ch=vch; + +select ch.* from ch, (select 'dEfg'::mvarchar as q) as p where chcol > p.q; +create index qq on ch (chcol); +set enable_seqscan=off; +select ch.* from ch, (select 'dEfg'::mvarchar as q) as p where chcol > p.q; +set enable_seqscan=on; + + +--\copy chvch to 'results/chvch.dump' binary +--truncate table chvch; +--\copy chvch from 'results/chvch.dump' binary + +--test joins +CREATE TABLE a (mchar2 MCHAR(2) NOT NULL); +CREATE TABLE c (mvarchar255 mvarchar NOT NULL); +SELECT * FROM a, c WHERE mchar2 = mvarchar255; +SELECT * FROM a, c WHERE mvarchar255 = mchar2; +DROP TABLE a; +DROP TABLE c; + +select * from (values + ('е'::mchar),('ё'),('еа'),('еб'),('ее'),('еж'),('ёа'),('ёб'),('ёё'),('ёж'),('ёе'),('её')) + z order by 1; + +select 'ё'::mchar = 'е'; +select 'Ё'::mchar = 'Е'; +select 'й'::mchar = 'и'; +select 'Й'::mchar = 'И'; + +select mvarchar_icase_cmp('ёа','еб'), mvarchar_icase_cmp('еб','ё'), + mvarchar_icase_cmp('ё', 'ёа'); diff --git a/contrib/mchar/sql/mvarchar.sql b/contrib/mchar/sql/mvarchar.sql new file mode 100644 index 00000000000..91b0981075d --- /dev/null +++ b/contrib/mchar/sql/mvarchar.sql @@ -0,0 +1,82 @@ +-- I/O tests + +select '1'::mvarchar; +select '2 '::mvarchar; +select '10 '::mvarchar; + +select '1'::mvarchar(2); +select '2 '::mvarchar(2); +select '3 '::mvarchar(2); +select '10 '::mvarchar(2); + +select ' '::mvarchar(10); +select ' '::mvarchar; + +-- operations & functions + +select length('1'::mvarchar); +select length('2 '::mvarchar); +select length('10 '::mvarchar); + +select length('1'::mvarchar(2)); +select length('2 '::mvarchar(2)); +select length('3 '::mvarchar(2)); +select length('10 '::mvarchar(2)); + +select length(' '::mvarchar(10)); +select length(' '::mvarchar); + +select 'asd'::mvarchar(10) || '>'::mvarchar(10); +select length('asd'::mvarchar(10) || '>'::mvarchar(10)); +select 'asd'::mvarchar(2) || '>'::mvarchar(10); +select length('asd'::mvarchar(2) || '>'::mvarchar(10)); + +-- Comparisons + +select 'asdf'::mvarchar = 'aSdf'::mvarchar; +select 'asdf'::mvarchar = 'aSdf '::mvarchar; +select 'asdf'::mvarchar = 'aSdf 1'::mvarchar(4); +select 'asdf'::mvarchar = 'aSdf 1'::mvarchar(5); +select 'asdf'::mvarchar = 'aSdf 1'::mvarchar(6); +select 'asdf'::mvarchar(3) = 'aSdf 1'::mvarchar(5); +select 'asdf'::mvarchar(3) = 'aSdf 1'::mvarchar(3); + +select 'asdf'::mvarchar < 'aSdf'::mvarchar; +select 'asdf'::mvarchar < 'aSdf '::mvarchar; +select 'asdf'::mvarchar < 'aSdf 1'::mvarchar(4); +select 'asdf'::mvarchar < 'aSdf 1'::mvarchar(5); +select 'asdf'::mvarchar < 'aSdf 1'::mvarchar(6); + +select 'asdf'::mvarchar <= 'aSdf'::mvarchar; +select 'asdf'::mvarchar <= 'aSdf '::mvarchar; +select 'asdf'::mvarchar <= 'aSdf 1'::mvarchar(4); +select 'asdf'::mvarchar <= 'aSdf 1'::mvarchar(5); +select 'asdf'::mvarchar <= 'aSdf 1'::mvarchar(6); + +select 'asdf'::mvarchar >= 'aSdf'::mvarchar; +select 'asdf'::mvarchar >= 'aSdf '::mvarchar; +select 'asdf'::mvarchar >= 'aSdf 1'::mvarchar(4); +select 'asdf'::mvarchar >= 'aSdf 1'::mvarchar(5); +select 'asdf'::mvarchar >= 'aSdf 1'::mvarchar(6); + +select 'asdf'::mvarchar > 'aSdf'::mvarchar; +select 'asdf'::mvarchar > 'aSdf '::mvarchar; +select 'asdf'::mvarchar > 'aSdf 1'::mvarchar(4); +select 'asdf'::mvarchar > 'aSdf 1'::mvarchar(5); +select 'asdf'::mvarchar > 'aSdf 1'::mvarchar(6); + +select max(vch) from chvch; +select min(vch) from chvch; + +select substr('1234567890'::mvarchar, 3) = '34567890' as "34567890"; +select substr('1234567890'::mvarchar, 4, 3) = '456' as "456"; + +select lower('asdfASDF'::mvarchar); +select upper('asdfASDF'::mvarchar); + +select 'asd'::mvarchar == 'aSd'::mvarchar; +select 'asd'::mvarchar == 'aCd'::mvarchar; +select 'asd'::mvarchar == NULL; +select NULL == 'aCd'::mvarchar; +select NULL::mvarchar == NULL; + diff --git a/contrib/online_analyze/COPYRIGHT b/contrib/online_analyze/COPYRIGHT new file mode 100644 index 00000000000..75fea1f35d6 --- /dev/null +++ b/contrib/online_analyze/COPYRIGHT @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2011 Teodor Sigaev <teodor@sigaev.ru> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY CONTRIBUTORS ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + diff --git a/contrib/online_analyze/Makefile b/contrib/online_analyze/Makefile new file mode 100644 index 00000000000..333add2b09b --- /dev/null +++ b/contrib/online_analyze/Makefile @@ -0,0 +1,16 @@ +MODULE_big = online_analyze +OBJS = online_analyze.o +#DATA_built = online_analyze.sql +DOCS = README.online_analyze +#REGRESS = online_analyze + +ifdef USE_PGXS +PGXS := $(shell pg_config --pgxs) +include $(PGXS) +else +subdir = contrib/online_analyze +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif + diff --git a/contrib/online_analyze/README.online_analyze b/contrib/online_analyze/README.online_analyze new file mode 100644 index 00000000000..d72f17db424 --- /dev/null +++ b/contrib/online_analyze/README.online_analyze @@ -0,0 +1,46 @@ +Module makes an analyze call immediately after INSERT/UPDATE/DELETE/SELECT INTO +for affected table(s). + +Supported versions of PostgreSQL: 8.4.*, 9.0.*, 9.1.*, 9.2.*, 9.3.*, 9.4*, 9.5*, + 9.6* + +Usage: LOAD 'online_analyze'; + +Custom variables (defaults values are shown): +online_analyze.enable = on + Enables on-line analyze + +online_analyze.local_tracking = off + Per backend tracking for temp tables (do not use system statistic) + +online_analyze.verbose = on + Execute ANALYZE VERBOSE + +online_analyze.scale_factor = 0.1 + Fraction of table size to start on-line analyze (similar to + autovacuum_analyze_scale_factor) + +online_analyze.threshold = 50 + Min number of row updates before on-line analyze (similar to + autovacuum_analyze_threshold) + +online_analyze.min_interval = 10000 + Minimum time interval between analyze call per table (in milliseconds) + +online_analyze.lower_limit = 0 + Min number of rows in table to analyze + +online_analyze.table_type = "all" + Type(s) of table for online analyze: all, persistent, temporary, none + +online_analyze.exclude_tables = "" + List of tables which will not online analyze + +online_analyze.include_tables = "" + List of tables which will online analyze + online_analyze.include_tables overwrites online_analyze.exclude_tables. + +online_analyze.capacity_threshold = 100000 + Maximum number of temporary tables to store in local cache + +Author: Teodor Sigaev <teodor@sigaev.ru> diff --git a/contrib/online_analyze/online_analyze.c b/contrib/online_analyze/online_analyze.c new file mode 100644 index 00000000000..ac268a00807 --- /dev/null +++ b/contrib/online_analyze/online_analyze.c @@ -0,0 +1,1316 @@ +/* + * Copyright (c) 2011 Teodor Sigaev <teodor@sigaev.ru> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY CONTRIBUTORS ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "postgres.h" + +#include "pgstat.h" +#include "access/transam.h" +#include "access/xact.h" +#include "catalog/namespace.h" +#include "commands/vacuum.h" +#include "executor/executor.h" +#include "nodes/nodes.h" +#include "nodes/parsenodes.h" +#include "storage/bufmgr.h" +#include "utils/builtins.h" +#include "utils/hsearch.h" +#include "utils/memutils.h" +#include "utils/lsyscache.h" +#include "utils/guc.h" +#if PG_VERSION_NUM >= 90200 +#include "catalog/pg_class.h" +#include "nodes/primnodes.h" +#include "tcop/utility.h" +#include "utils/rel.h" +#include "utils/relcache.h" +#include "utils/timestamp.h" +#if PG_VERSION_NUM >= 90500 +#include "nodes/makefuncs.h" +#if PG_VERSION_NUM >= 100000 +#include "utils/varlena.h" +#include "utils/regproc.h" +#if PG_VERSION_NUM >= 130000 +#include "common/hashfn.h" +#endif +#endif +#endif +#endif + +#ifdef PG_MODULE_MAGIC +PG_MODULE_MAGIC; +#endif + +static bool online_analyze_enable = true; +static bool online_analyze_local_tracking = false; +static bool online_analyze_verbose = true; +static double online_analyze_scale_factor = 0.1; +static int online_analyze_threshold = 50; +static int online_analyze_capacity_threshold = 100000; +static double online_analyze_min_interval = 10000; +static int online_analyze_lower_limit = 0; + +static ExecutorEnd_hook_type oldExecutorEndHook = NULL; +#if PG_VERSION_NUM >= 90200 +static ProcessUtility_hook_type oldProcessUtilityHook = NULL; +#endif + +#if PG_VERSION_NUM >= 120000 +#define VACOPT_NOWAIT VACOPT_SKIP_LOCKED +#endif + +typedef enum CmdKind +{ + CK_SELECT = CMD_SELECT, + CK_UPDATE = CMD_UPDATE, + CK_INSERT = CMD_INSERT, + CK_DELETE = CMD_DELETE, + CK_TRUNCATE, + CK_FASTTRUNCATE, + CK_CREATE, + CK_ANALYZE, + CK_VACUUM +} CmdKind; + + +typedef enum +{ + OATT_ALL = 0x03, + OATT_PERSISTENT = 0x01, + OATT_TEMPORARY = 0x02, + OATT_NONE = 0x00 +} OnlineAnalyzeTableType; + +static const struct config_enum_entry online_analyze_table_type_options[] = +{ + {"all", OATT_ALL, false}, + {"persistent", OATT_PERSISTENT, false}, + {"temporary", OATT_TEMPORARY, false}, + {"none", OATT_NONE, false}, + {NULL, 0, false}, +}; + +static int online_analyze_table_type = (int)OATT_ALL; + +typedef struct TableList { + int nTables; + Oid *tables; + char *tableStr; +} TableList; + +static TableList excludeTables = {0, NULL, NULL}; +static TableList includeTables = {0, NULL, NULL}; + +typedef struct OnlineAnalyzeTableStat { + Oid tableid; + bool rereadStat; + PgStat_Counter n_tuples; + PgStat_Counter changes_since_analyze; + TimestampTz autovac_analyze_timestamp; + TimestampTz analyze_timestamp; +} OnlineAnalyzeTableStat; + +static MemoryContext onlineAnalyzeMemoryContext = NULL; +static HTAB *relstats = NULL; + +static void relstatsInit(void); + +#if PG_VERSION_NUM < 100000 +static int +oid_cmp(const void *a, const void *b) +{ + if (*(Oid*)a == *(Oid*)b) + return 0; + return (*(Oid*)a > *(Oid*)b) ? 1 : -1; +} +#endif + +static const char * +tableListAssign(const char * newval, bool doit, TableList *tbl) +{ + char *rawname; + List *namelist; + ListCell *l; + Oid *newOids = NULL; + int nOids = 0, + i = 0; + + rawname = pstrdup(newval); + + if (!SplitIdentifierString(rawname, ',', &namelist)) + goto cleanup; + + if (doit) + { + nOids = list_length(namelist); + newOids = malloc(sizeof(Oid) * (nOids+1)); + if (!newOids) + elog(ERROR,"could not allocate %d bytes", + (int)(sizeof(Oid) * (nOids+1))); + } + + foreach(l, namelist) + { + char *curname = (char *) lfirst(l); +#if PG_VERSION_NUM >= 90200 + Oid relOid = RangeVarGetRelid(makeRangeVarFromNameList( + stringToQualifiedNameList(curname)), NoLock, true); +#else + Oid relOid = RangeVarGetRelid(makeRangeVarFromNameList( + stringToQualifiedNameList(curname)), true); +#endif + + if (relOid == InvalidOid) + { +#if PG_VERSION_NUM >= 90100 + if (doit == false) +#endif + elog(WARNING,"'%s' does not exist", curname); + continue; + } + else if ( get_rel_relkind(relOid) != RELKIND_RELATION ) + { +#if PG_VERSION_NUM >= 90100 + if (doit == false) +#endif + elog(WARNING,"'%s' is not an table", curname); + continue; + } + else if (doit) + { + newOids[i++] = relOid; + } + } + + if (doit) + { + tbl->nTables = i; + if (tbl->tables) + free(tbl->tables); + tbl->tables = newOids; + if (tbl->nTables > 1) + qsort(tbl->tables, tbl->nTables, sizeof(tbl->tables[0]), oid_cmp); + } + + pfree(rawname); + list_free(namelist); + + return newval; + +cleanup: + if (newOids) + free(newOids); + pfree(rawname); + list_free(namelist); + return NULL; +} + +#if PG_VERSION_NUM >= 90100 +static bool +excludeTablesCheck(char **newval, void **extra, GucSource source) +{ + char *val; + + val = (char*)tableListAssign(*newval, false, &excludeTables); + + if (val) + { + *newval = val; + return true; + } + + return false; +} + +static void +excludeTablesAssign(const char *newval, void *extra) +{ + tableListAssign(newval, true, &excludeTables); +} + +static bool +includeTablesCheck(char **newval, void **extra, GucSource source) +{ + char *val; + + val = (char*)tableListAssign(*newval, false, &includeTables); + + if (val) + { + *newval = val; + return true; + } + + return false; +} + +static void +includeTablesAssign(const char *newval, void *extra) +{ + tableListAssign(newval, true, &excludeTables); +} + +#else /* PG_VERSION_NUM < 90100 */ + +static const char * +excludeTablesAssign(const char * newval, bool doit, GucSource source) +{ + return tableListAssign(newval, doit, &excludeTables); +} + +static const char * +includeTablesAssign(const char * newval, bool doit, GucSource source) +{ + return tableListAssign(newval, doit, &includeTables); +} + +#endif + +static const char* +tableListShow(TableList *tbl) +{ + char *val, *ptr; + int i, + len; + + len = 1 /* \0 */ + tbl->nTables * (2 * NAMEDATALEN + 2 /* ', ' */ + 1 /* . */); + ptr = val = palloc(len); + *ptr ='\0'; + for(i=0; i<tbl->nTables; i++) + { + char *relname = get_rel_name(tbl->tables[i]); + Oid nspOid = get_rel_namespace(tbl->tables[i]); + char *nspname = get_namespace_name(nspOid); + + if ( relname == NULL || nspOid == InvalidOid || nspname == NULL ) + continue; + + ptr += snprintf(ptr, len - (ptr - val), "%s%s.%s", + (i==0) ? "" : ", ", + nspname, relname); + } + + return val; +} + +static const char* +excludeTablesShow(void) +{ + return tableListShow(&excludeTables); +} + +static const char* +includeTablesShow(void) +{ + return tableListShow(&includeTables); +} + +static bool +matchOid(TableList *tbl, Oid oid) +{ + Oid *StopLow = tbl->tables, + *StopHigh = tbl->tables + tbl->nTables, + *StopMiddle; + + /* Loop invariant: StopLow <= val < StopHigh */ + while (StopLow < StopHigh) + { + StopMiddle = StopLow + ((StopHigh - StopLow) >> 1); + + if (*StopMiddle == oid) + return true; + else if (*StopMiddle < oid) + StopLow = StopMiddle + 1; + else + StopHigh = StopMiddle; + } + + return false; +} + +#if PG_VERSION_NUM >= 90500 +static RangeVar* +makeRangeVarFromOid(Oid relOid) +{ + return makeRangeVar( + get_namespace_name(get_rel_namespace(relOid)), + get_rel_name(relOid), + -1 + ); + +} +#endif + +static void +makeAnalyze(Oid relOid, CmdKind operation, int64 naffected) +{ + TimestampTz now = GetCurrentTimestamp(); + Relation rel; + OnlineAnalyzeTableType reltype; + bool found = false, + newTable = false; + OnlineAnalyzeTableStat *rstat, + dummyrstat; + PgStat_StatTabEntry *tabentry = NULL; + + if (relOid == InvalidOid) + return; + + if (naffected == 0) + /* return if there is no changes */ + return; + else if (naffected < 0) + /* number if affected rows is unknown */ + naffected = 0; + + rel = RelationIdGetRelation(relOid); + if (rel->rd_rel->relkind != RELKIND_RELATION) + { + RelationClose(rel); + return; + } + + reltype = +#if PG_VERSION_NUM >= 90100 + (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP) +#else + (rel->rd_istemp || rel->rd_islocaltemp) +#endif + ? OATT_TEMPORARY : OATT_PERSISTENT; + + RelationClose(rel); + + /* + * includeTables overwrites excludeTables + */ + switch(online_analyze_table_type) + { + case OATT_ALL: + if (get_rel_relkind(relOid) != RELKIND_RELATION || + (matchOid(&excludeTables, relOid) == true && + matchOid(&includeTables, relOid) == false)) + return; + break; + case OATT_NONE: + if (get_rel_relkind(relOid) != RELKIND_RELATION || + matchOid(&includeTables, relOid) == false) + return; + break; + case OATT_TEMPORARY: + case OATT_PERSISTENT: + default: + /* + * skip analyze if relation's type doesn't not match + * online_analyze_table_type + */ + if ((online_analyze_table_type & reltype) == 0 || + matchOid(&excludeTables, relOid) == true) + { + if (matchOid(&includeTables, relOid) == false) + return; + } + break; + } + + /* + * Do not store data about persistent table in local memory because we + * could not track changes of them: they could be changed by another + * backends. So always get a pgstat table entry. + */ + if (reltype == OATT_TEMPORARY) + rstat = hash_search(relstats, &relOid, HASH_ENTER, &found); + else + rstat = &dummyrstat; /* found == false for following if */ + + if (!found) + { + MemSet(rstat, 0, sizeof(*rstat)); + rstat->tableid = relOid; + newTable = true; + } + + if (operation == CK_VACUUM) + { + /* force reread because vacuum could change n_tuples */ + rstat->rereadStat = true; + return; + } + else if (operation == CK_ANALYZE) + { + /* only analyze */ + rstat->changes_since_analyze = 0; + rstat->analyze_timestamp = now; + if (newTable) + rstat->rereadStat = true; + return; + } + + Assert(rstat->tableid == relOid); + + if ( + /* do not reread data if it was a truncation */ + operation != CK_TRUNCATE && operation != CK_FASTTRUNCATE && + /* read for persistent table and for temp teble if it allowed */ + (reltype == OATT_PERSISTENT || online_analyze_local_tracking == false) && + /* read only for new table or we know that it's needed */ + (newTable == true || rstat->rereadStat == true) + ) + { + rstat->rereadStat = false; + + tabentry = pgstat_fetch_stat_tabentry(relOid); + + if (tabentry) + { + rstat->n_tuples = tabentry->n_dead_tuples + tabentry->n_live_tuples; + rstat->changes_since_analyze = +#if PG_VERSION_NUM >= 90000 + tabentry->changes_since_analyze; +#else + tabentry->n_live_tuples + tabentry->n_dead_tuples - + tabentry->last_anl_tuples; +#endif + rstat->autovac_analyze_timestamp = + tabentry->autovac_analyze_timestamp; + rstat->analyze_timestamp = tabentry->analyze_timestamp; + } + } + + if (newTable || + /* force analyze after truncate, fasttruncate already did analyze */ + operation == CK_TRUNCATE || ( + /* do not analyze too often, if both stamps are exceeded the go */ + TimestampDifferenceExceeds(rstat->analyze_timestamp, now, online_analyze_min_interval) && + TimestampDifferenceExceeds(rstat->autovac_analyze_timestamp, now, online_analyze_min_interval) && + /* do not analyze too small tables */ + rstat->n_tuples + rstat->changes_since_analyze + naffected > online_analyze_lower_limit && + /* be in sync with relation_needs_vacanalyze */ + ((double)(rstat->changes_since_analyze + naffected)) >= + online_analyze_scale_factor * ((double)rstat->n_tuples) + + (double)online_analyze_threshold)) + { +#if PG_VERSION_NUM < 90500 + VacuumStmt vacstmt; +#else + VacuumParams vacstmt; +#endif + TimestampTz startStamp, endStamp; + int flags; + + + memset(&startStamp, 0, sizeof(startStamp)); /* keep compiler quiet */ + + memset(&vacstmt, 0, sizeof(vacstmt)); + + vacstmt.freeze_min_age = -1; + vacstmt.freeze_table_age = -1; /* ??? */ + +#if PG_VERSION_NUM < 90500 + vacstmt.type = T_VacuumStmt; + vacstmt.relation = NULL; + vacstmt.va_cols = NIL; +#if PG_VERSION_NUM >= 90000 + vacstmt.options = VACOPT_ANALYZE; + if (online_analyze_verbose) + vacstmt.options |= VACOPT_VERBOSE; +#else + vacstmt.vacuum = vacstmt.full = false; + vacstmt.analyze = true; + vacstmt.verbose = online_analyze_verbose; +#endif +#else + vacstmt.multixact_freeze_min_age = -1; + vacstmt.multixact_freeze_table_age = -1; + vacstmt.log_min_duration = -1; +#endif + + + if (online_analyze_verbose) + startStamp = GetCurrentTimestamp(); + + flags = VACOPT_ANALYZE | VACOPT_NOWAIT | + ((online_analyze_verbose) ? VACOPT_VERBOSE : 0); + +#if PG_VERSION_NUM >= 120000 + vacstmt.options = flags; +#endif + analyze_rel(relOid, +#if PG_VERSION_NUM < 90500 + &vacstmt +#if PG_VERSION_NUM >= 90018 + , true +#endif + , GetAccessStrategy(BAS_VACUUM) +#if (PG_VERSION_NUM >= 90000) && (PG_VERSION_NUM < 90004) + , true +#endif +#else + makeRangeVarFromOid(relOid), +#if PG_VERSION_NUM < 120000 + flags, +#endif + &vacstmt, NULL, true, GetAccessStrategy(BAS_VACUUM) +#endif + ); + + /* Make changes visible to subsequent calls */ + CommandCounterIncrement(); + + if (online_analyze_verbose) + { + long secs; + int microsecs; + + endStamp = GetCurrentTimestamp(); + TimestampDifference(startStamp, endStamp, &secs, µ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; + /* FALLTHROUGH */ + case CK_DELETE: + rstat->rereadStat = (reltype == OATT_PERSISTENT); + break; + case CK_TRUNCATE: + case CK_FASTTRUNCATE: + rstat->rereadStat = false; + rstat->n_tuples = 0; + break; + default: + break; + } + + /* update last analyze timestamp in local memory of backend */ + if (tabentry) + { + tabentry->analyze_timestamp = now; + tabentry->changes_since_analyze = 0; + } +#if 0 + /* force reload stat for new table */ + if (newTable) + pgstat_clear_snapshot(); +#endif + } + else + { +#if PG_VERSION_NUM >= 90000 + if (tabentry) + tabentry->changes_since_analyze += naffected; +#endif + switch(operation) + { + case CK_CREATE: + case CK_INSERT: + rstat->changes_since_analyze += naffected; + rstat->n_tuples += naffected; + break; + case CK_UPDATE: + rstat->changes_since_analyze += 2 * naffected; + rstat->n_tuples += naffected; + break; + case CK_DELETE: + rstat->changes_since_analyze += naffected; + break; + case CK_TRUNCATE: + case CK_FASTTRUNCATE: + rstat->changes_since_analyze = 0; + rstat->n_tuples = 0; + break; + default: + break; + } + } + + /* Reset local cache if we are over limit */ + if (hash_get_num_entries(relstats) > online_analyze_capacity_threshold) + relstatsInit(); +} + +static Const* +isFastTruncateCall(QueryDesc *queryDesc) +{ + TargetEntry *te; + FuncExpr *fe; + Const *constval; + + if (!( + queryDesc->plannedstmt && + queryDesc->operation == CMD_SELECT && + queryDesc->plannedstmt->planTree && + queryDesc->plannedstmt->planTree->targetlist && + list_length(queryDesc->plannedstmt->planTree->targetlist) == 1 + )) + return NULL; + + te = linitial(queryDesc->plannedstmt->planTree->targetlist); + + if (!IsA(te, TargetEntry)) + return NULL; + + fe = (FuncExpr*)te->expr; + + if (!( + fe && IsA(fe, FuncExpr) && + fe->funcid >= FirstNormalObjectId && + fe->funcretset == false && + fe->funcresulttype == VOIDOID && + fe->funcvariadic == false && + list_length(fe->args) == 1 + )) + return NULL; + + constval = linitial(fe->args); + + if (!( + IsA(constval,Const) && + constval->consttype == TEXTOID && + strcmp(get_func_name(fe->funcid), "fasttruncate") == 0 + )) + return NULL; + + return constval; +} + + +extern PGDLLIMPORT void onlineAnalyzeHooker(QueryDesc *queryDesc); +void +onlineAnalyzeHooker(QueryDesc *queryDesc) +{ + int64 naffected = -1; + Const *constval; + + if (queryDesc->estate) + naffected = queryDesc->estate->es_processed; + +#if PG_VERSION_NUM >= 90200 + if (online_analyze_enable && + (constval = isFastTruncateCall(queryDesc)) != NULL) + { + Datum tblnamed = constval->constvalue; + char *tblname = text_to_cstring(DatumGetTextP(tblnamed)); + RangeVar *tblvar = + makeRangeVarFromNameList(stringToQualifiedNameList(tblname)); + + makeAnalyze(RangeVarGetRelid(tblvar, + NoLock, + false), + CK_FASTTRUNCATE, -1); + } +#endif + + if (online_analyze_enable && queryDesc->plannedstmt && + (queryDesc->operation == CMD_INSERT || + queryDesc->operation == CMD_UPDATE || + queryDesc->operation == CMD_DELETE +#if PG_VERSION_NUM < 90200 + || (queryDesc->operation == CMD_SELECT && + queryDesc->plannedstmt->intoClause) +#endif + )) + { +#if PG_VERSION_NUM < 90200 + if (queryDesc->operation == CMD_SELECT) + { + Oid relOid = RangeVarGetRelid(queryDesc->plannedstmt->intoClause->rel, true); + + makeAnalyze(relOid, queryDesc->operation, naffected); + } + else +#endif + if (queryDesc->plannedstmt->resultRelations && + queryDesc->plannedstmt->rtable) + { + ListCell *l; + + foreach(l, queryDesc->plannedstmt->resultRelations) + { + int n = lfirst_int(l); + RangeTblEntry *rte = list_nth(queryDesc->plannedstmt->rtable, n-1); + + if (rte->rtekind == RTE_RELATION) + makeAnalyze(rte->relid, (CmdKind)queryDesc->operation, naffected); + } + } + } + + if (oldExecutorEndHook) + oldExecutorEndHook(queryDesc); + else + standard_ExecutorEnd(queryDesc); +} + +static List *toremove = NIL; + +/* + * removeTable called on transaction end, see call RegisterXactCallback() below + */ +static void +removeTable(XactEvent event, void *arg) +{ + ListCell *cell; + + switch(event) + { + case XACT_EVENT_COMMIT: + break; + case XACT_EVENT_ABORT: + toremove = NIL; + default: + return; + } + + foreach(cell, toremove) + { + Oid relOid = lfirst_oid(cell); + + hash_search(relstats, &relOid, HASH_REMOVE, NULL); + } + + toremove = NIL; +} + +#if PG_VERSION_NUM >= 120000 +static int +parse_vacuum_opt(VacuumStmt *vacstmt) +{ + int options = vacstmt->is_vacuumcmd ? VACOPT_VACUUM : VACOPT_ANALYZE; + ListCell *lc; + + foreach(lc, vacstmt->options) + { + DefElem *opt = (DefElem *) lfirst(lc); + + /* Parse common options for VACUUM and ANALYZE */ + if (strcmp(opt->defname, "verbose") == 0) + options |= VACOPT_VERBOSE; + else if (strcmp(opt->defname, "skip_locked") == 0) + options |= VACOPT_SKIP_LOCKED; + else if (strcmp(opt->defname, "analyze") == 0) + options |= VACOPT_ANALYZE; + else if (strcmp(opt->defname, "freeze") == 0) + options |= VACOPT_FREEZE; + else if (strcmp(opt->defname, "full") == 0) + options |= VACOPT_FULL; + else if (strcmp(opt->defname, "disable_page_skipping") == 0) + options |= VACOPT_DISABLE_PAGE_SKIPPING; + } + + return options; +} +#endif + + +#if PG_VERSION_NUM >= 90200 +static void +onlineAnalyzeHookerUtility( +#if PG_VERSION_NUM >= 100000 + PlannedStmt *pstmt, +#else + Node *parsetree, +#endif + const char *queryString, +#if PG_VERSION_NUM >= 90300 + ProcessUtilityContext context, ParamListInfo params, +#if PG_VERSION_NUM >= 100000 + QueryEnvironment *queryEnv, +#endif +#else + ParamListInfo params, bool isTopLevel, +#endif + DestReceiver *dest, +#if PG_VERSION_NUM >= 130000 + QueryCompletion *completionTag +#else + char *completionTag +#endif +) { + List *tblnames = NIL; + CmdKind op = CK_INSERT; +#if PG_VERSION_NUM >= 100000 + Node *parsetree = NULL; + + if (pstmt->commandType == CMD_UTILITY) + parsetree = pstmt->utilityStmt; +#endif + + if (parsetree && online_analyze_enable) + { + if (IsA(parsetree, CreateTableAsStmt) && + ((CreateTableAsStmt*)parsetree)->into) + { + tblnames = + list_make1((RangeVar*)copyObject(((CreateTableAsStmt*)parsetree)->into->rel)); + op = CK_CREATE; + } + else if (IsA(parsetree, TruncateStmt)) + { + tblnames = list_copy(((TruncateStmt*)parsetree)->relations); + op = CK_TRUNCATE; + } + else if (IsA(parsetree, DropStmt) && + ((DropStmt*)parsetree)->removeType == OBJECT_TABLE) + { + ListCell *cell; + + foreach(cell, ((DropStmt*)parsetree)->objects) + { + List *relname = (List *) lfirst(cell); + RangeVar *rel = makeRangeVarFromNameList(relname); + Oid relOid = RangeVarGetRelid(rel, NoLock, true); + + if (OidIsValid(relOid)) + { + MemoryContext ctx; + + ctx = MemoryContextSwitchTo(TopTransactionContext); + toremove = lappend_oid(toremove, relOid); + MemoryContextSwitchTo(ctx); + } + } + } + else if (IsA(parsetree, VacuumStmt)) + { + VacuumStmt *vac = (VacuumStmt*)parsetree; + int options = +#if PG_VERSION_NUM >= 120000 + parse_vacuum_opt(vac) +#else + vac->options +#endif + ; + + +#if PG_VERSION_NUM >= 110000 + tblnames = vac->rels; +#else + if (vac->relation) + tblnames = list_make1(vac->relation); +#endif + + if (options & (VACOPT_VACUUM | VACOPT_FULL | VACOPT_FREEZE)) + { + /* optionally with analyze */ + op = CK_VACUUM; + + /* drop all collected stat */ + if (tblnames == NIL) + relstatsInit(); + } + else if (options & VACOPT_ANALYZE) + { + op = CK_ANALYZE; + + /* should reset all counters */ + if (tblnames == NIL) + { + HASH_SEQ_STATUS hs; + OnlineAnalyzeTableStat *rstat; + TimestampTz now = GetCurrentTimestamp(); + + hash_seq_init(&hs, relstats); + + while((rstat = hash_seq_search(&hs)) != NULL) + { + rstat->changes_since_analyze = 0; + rstat->analyze_timestamp = now; + } + } + } + else + tblnames = NIL; + } + } + +#if PG_VERSION_NUM >= 100000 +#define parsetree pstmt +#endif + + if (oldProcessUtilityHook) + oldProcessUtilityHook(parsetree, queryString, +#if PG_VERSION_NUM >= 90300 + context, params, +#if PG_VERSION_NUM >= 100000 + queryEnv, +#endif +#else + params, isTopLevel, +#endif + dest, completionTag); + else + standard_ProcessUtility(parsetree, queryString, +#if PG_VERSION_NUM >= 90300 + context, params, +#if PG_VERSION_NUM >= 100000 + queryEnv, +#endif +#else + params, isTopLevel, +#endif + dest, completionTag); + +#if PG_VERSION_NUM >= 100000 +#undef parsetree +#endif + + if (tblnames) { + ListCell *l; + + foreach(l, tblnames) + { + RangeVar *tblname = +#if PG_VERSION_NUM >= 110000 + (IsA(lfirst(l), VacuumRelation)) ? + ((VacuumRelation*)lfirst(l))->relation : +#endif + (RangeVar*)lfirst(l); + Oid tblOid; + + Assert(IsA(tblname, RangeVar)); + + tblOid = RangeVarGetRelid(tblname, NoLock, true); + makeAnalyze(tblOid, op, -1); + } + } +} +#endif + + +static void +relstatsInit(void) +{ + HASHCTL hash_ctl; + int flags = 0; + + MemSet(&hash_ctl, 0, sizeof(hash_ctl)); + + hash_ctl.hash = oid_hash; + flags |= HASH_FUNCTION; + + if (onlineAnalyzeMemoryContext) + { + Assert(relstats != NULL); + MemoryContextReset(onlineAnalyzeMemoryContext); + } + else + { + Assert(relstats == NULL); + +#if PG_VERSION_NUM < 90600 + onlineAnalyzeMemoryContext = + AllocSetContextCreate(CacheMemoryContext, + "online_analyze storage context", + ALLOCSET_DEFAULT_MINSIZE, + ALLOCSET_DEFAULT_INITSIZE, + ALLOCSET_DEFAULT_MAXSIZE + ); +#else + onlineAnalyzeMemoryContext = + AllocSetContextCreate(CacheMemoryContext, + "online_analyze storage context", ALLOCSET_DEFAULT_SIZES); +#endif + } + + hash_ctl.hcxt = onlineAnalyzeMemoryContext; + flags |= HASH_CONTEXT; + + hash_ctl.keysize = sizeof(Oid); + + hash_ctl.entrysize = sizeof(OnlineAnalyzeTableStat); + flags |= HASH_ELEM; + + relstats = hash_create("online_analyze storage", 1024, &hash_ctl, flags); +} + +void _PG_init(void); +void +_PG_init(void) +{ + relstatsInit(); + + oldExecutorEndHook = ExecutorEnd_hook; + + ExecutorEnd_hook = onlineAnalyzeHooker; + +#if PG_VERSION_NUM >= 90200 + oldProcessUtilityHook = ProcessUtility_hook; + + ProcessUtility_hook = onlineAnalyzeHookerUtility; +#endif + + + DefineCustomBoolVariable( + "online_analyze.enable", + "Enable on-line analyze", + "Enables analyze of table directly after insert/update/delete/select into", + &online_analyze_enable, +#if PG_VERSION_NUM >= 80400 + online_analyze_enable, +#endif + PGC_USERSET, +#if PG_VERSION_NUM >= 80400 + GUC_NOT_IN_SAMPLE, +#if PG_VERSION_NUM >= 90100 + NULL, +#endif +#endif + NULL, + NULL + ); + + DefineCustomBoolVariable( + "online_analyze.local_tracking", + "Per backend tracking", + "Per backend tracking for temp tables (do not use system statistic)", + &online_analyze_local_tracking, +#if PG_VERSION_NUM >= 80400 + online_analyze_local_tracking, +#endif + PGC_USERSET, +#if PG_VERSION_NUM >= 80400 + GUC_NOT_IN_SAMPLE, +#if PG_VERSION_NUM >= 90100 + NULL, +#endif +#endif + NULL, + NULL + ); + + DefineCustomBoolVariable( + "online_analyze.verbose", + "Verbosity of on-line analyze", + "Make ANALYZE VERBOSE after table's changes", + &online_analyze_verbose, +#if PG_VERSION_NUM >= 80400 + online_analyze_verbose, +#endif + PGC_USERSET, +#if PG_VERSION_NUM >= 80400 + GUC_NOT_IN_SAMPLE, +#if PG_VERSION_NUM >= 90100 + NULL, +#endif +#endif + NULL, + NULL + ); + + DefineCustomRealVariable( + "online_analyze.scale_factor", + "fraction of table size to start on-line analyze", + "fraction of table size to start on-line analyze", + &online_analyze_scale_factor, +#if PG_VERSION_NUM >= 80400 + online_analyze_scale_factor, +#endif + 0.0, + 1.0, + PGC_USERSET, +#if PG_VERSION_NUM >= 80400 + GUC_NOT_IN_SAMPLE, +#if PG_VERSION_NUM >= 90100 + NULL, +#endif +#endif + NULL, + NULL + ); + + DefineCustomIntVariable( + "online_analyze.threshold", + "min number of row updates before on-line analyze", + "min number of row updates before on-line analyze", + &online_analyze_threshold, +#if PG_VERSION_NUM >= 80400 + online_analyze_threshold, +#endif + 0, + 0x7fffffff, + PGC_USERSET, +#if PG_VERSION_NUM >= 80400 + GUC_NOT_IN_SAMPLE, +#if PG_VERSION_NUM >= 90100 + NULL, +#endif +#endif + NULL, + NULL + ); + + DefineCustomIntVariable( + "online_analyze.capacity_threshold", + "Max local cache table capacity", + "Max local cache table capacity", + &online_analyze_capacity_threshold, +#if PG_VERSION_NUM >= 80400 + online_analyze_capacity_threshold, +#endif + 0, + 0x7fffffff, + PGC_USERSET, +#if PG_VERSION_NUM >= 80400 + GUC_NOT_IN_SAMPLE, +#if PG_VERSION_NUM >= 90100 + NULL, +#endif +#endif + NULL, + NULL + ); + + DefineCustomRealVariable( + "online_analyze.min_interval", + "minimum time interval between analyze call (in milliseconds)", + "minimum time interval between analyze call (in milliseconds)", + &online_analyze_min_interval, +#if PG_VERSION_NUM >= 80400 + online_analyze_min_interval, +#endif + 0.0, + 1e30, + PGC_USERSET, +#if PG_VERSION_NUM >= 80400 + GUC_NOT_IN_SAMPLE, +#if PG_VERSION_NUM >= 90100 + NULL, +#endif +#endif + NULL, + NULL + ); + + DefineCustomEnumVariable( + "online_analyze.table_type", + "Type(s) of table for online analyze: all(default), persistent, temporary, none", + NULL, + &online_analyze_table_type, +#if PG_VERSION_NUM >= 80400 + online_analyze_table_type, +#endif + online_analyze_table_type_options, + PGC_USERSET, +#if PG_VERSION_NUM >= 80400 + GUC_NOT_IN_SAMPLE, +#if PG_VERSION_NUM >= 90100 + NULL, +#endif +#endif + NULL, + NULL + ); + + DefineCustomStringVariable( + "online_analyze.exclude_tables", + "List of tables which will not online analyze", + NULL, + &excludeTables.tableStr, +#if PG_VERSION_NUM >= 80400 + "", +#endif + PGC_USERSET, + 0, +#if PG_VERSION_NUM >= 90100 + excludeTablesCheck, + excludeTablesAssign, +#else + excludeTablesAssign, +#endif + excludeTablesShow + ); + + DefineCustomStringVariable( + "online_analyze.include_tables", + "List of tables which will online analyze", + NULL, + &includeTables.tableStr, +#if PG_VERSION_NUM >= 80400 + "", +#endif + PGC_USERSET, + 0, +#if PG_VERSION_NUM >= 90100 + includeTablesCheck, + includeTablesAssign, +#else + includeTablesAssign, +#endif + includeTablesShow + ); + + DefineCustomIntVariable( + "online_analyze.lower_limit", + "min number of rows in table to analyze", + "min number of rows in table to analyze", + &online_analyze_lower_limit, +#if PG_VERSION_NUM >= 80400 + online_analyze_lower_limit, +#endif + 0, + 0x7fffffff, + PGC_USERSET, +#if PG_VERSION_NUM >= 80400 + GUC_NOT_IN_SAMPLE, +#if PG_VERSION_NUM >= 90100 + NULL, +#endif +#endif + NULL, + NULL + ); + + RegisterXactCallback(removeTable, NULL); +} + +void _PG_fini(void); +void +_PG_fini(void) +{ + ExecutorEnd_hook = oldExecutorEndHook; +#if PG_VERSION_NUM >= 90200 + ProcessUtility_hook = oldProcessUtilityHook; +#endif + + if (excludeTables.tables) + free(excludeTables.tables); + if (includeTables.tables) + free(includeTables.tables); + + excludeTables.tables = includeTables.tables = NULL; + excludeTables.nTables = includeTables.nTables = 0; +} diff --git a/contrib/plantuner/COPYRIGHT b/contrib/plantuner/COPYRIGHT new file mode 100644 index 00000000000..6e4705bc561 --- /dev/null +++ b/contrib/plantuner/COPYRIGHT @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2009 Teodor Sigaev <teodor@sigaev.ru> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY CONTRIBUTORS ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + diff --git a/contrib/plantuner/Makefile b/contrib/plantuner/Makefile new file mode 100644 index 00000000000..f2e8350e84c --- /dev/null +++ b/contrib/plantuner/Makefile @@ -0,0 +1,15 @@ +MODULE_big = plantuner +DOCS = README.plantuner +REGRESS = plantuner +OBJS=plantuner.o + +ifdef USE_PGXS +PGXS = $(shell pg_config --pgxs) +include $(PGXS) +else +subdir = contrib/plantuner +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global + +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/plantuner/README.plantuner b/contrib/plantuner/README.plantuner new file mode 100644 index 00000000000..17c8ba010b8 --- /dev/null +++ b/contrib/plantuner/README.plantuner @@ -0,0 +1,99 @@ +Plantuner - enable planner hints + + contrib/plantuner is a contribution module for PostgreSQL 8.4+, which + enable planner hints. + + All work was done by Teodor Sigaev (teodor@sigaev.ru) and Oleg Bartunov + (oleg@sai.msu.su). + + Sponsor: Nomao project (http://www.nomao.com) + +Motivation + + Whether somebody think it's bad or not, but sometime it's very + interesting to be able to control planner (provide hints, which tells + optimizer to ignore its algorithm in part), which is currently + impossible in POstgreSQL. Oracle, for example, has over 120 hints, SQL + Server also provides hints. + + This first version of plantuner provides a possibility to hide + specified indexes from PostgreSQL planner, so it will not use them. + + There are many situation, when developer want to temporarily disable + specific index(es), without dropping them, or to instruct planner to + use specific index. + + Next, for some workload PostgreSQL could be too pessimistic for + newly created tables and assumes much more rows in table than + it actually has. If plantuner.fix_empty_table GUC variable is set + to true then module will set to zero number of pages/tuples of + table which hasn't blocks in file. + +Installation + + * Get latest source of plantuner from CVS Repository + * gmake && gmake install && gmake installcheck + +Syntax + plantuner.forbid_index (deprecated) + plantuner.disable_index + List of indexes invisible to planner + plantuner.enable_index + List of indexes visible to planner even they are hided + by plantuner.disable_index. + plantuner.only_index + List of explicitly enabled indexes (overload plantuner.disable_index + and plantuner.enable_index), so, only indexes in this list are allowed. + +Usage + + To enable the module you can either load shared library 'plantuner' in + psql session or specify 'shared_preload_libraries' option in + postgresql.conf. +=# LOAD 'plantuner'; +=# create table test(id int); +=# create index id_idx on test(id); +=# create index id_idx2 on test(id); +=# \d test + Table "public.test" + Column | Type | Modifiers +--------+---------+----------- + id | integer | +Indexes: + "id_idx" btree (id) + "id_idx2" btree (id) +=# explain select id from test where id=1; + QUERY PLAN +----------------------------------------------------------------------- + Bitmap Heap Scan on test (cost=4.34..15.03 rows=12 width=4) + Recheck Cond: (id = 1) + -> Bitmap Index Scan on id_idx2 (cost=0.00..4.34 rows=12 width=0) + Index Cond: (id = 1) +(4 rows) +=# set enable_seqscan=off; +=# set plantuner.disable_index='id_idx2'; +=# explain select id from test where id=1; + QUERY PLAN +---------------------------------------------------------------------- + Bitmap Heap Scan on test (cost=4.34..15.03 rows=12 width=4) + Recheck Cond: (id = 1) + -> Bitmap Index Scan on id_idx (cost=0.00..4.34 rows=12 width=0) + Index Cond: (id = 1) +(4 rows) +=# set plantuner.disable_index='id_idx2,id_idx'; +=# explain select id from test where id=1; + QUERY PLAN +------------------------------------------------------------------------- + Seq Scan on test (cost=10000000000.00..10000000040.00 rows=12 width=4) + Filter: (id = 1) +(2 rows) +=# set plantuner.enable_index='id_idx'; +=# explain select id from test where id=1; + QUERY PLAN +----------------------------------------------------------------------- + Bitmap Heap Scan on test (cost=4.34..15.03 rows=12 width=4) + Recheck Cond: (id = 1) + -> Bitmap Index Scan on id_idx (cost=0.00..4.34 rows=12 width=0) + Index Cond: (id = 1) +(4 rows) + diff --git a/contrib/plantuner/expected/plantuner.out b/contrib/plantuner/expected/plantuner.out new file mode 100644 index 00000000000..70d2bcaaef2 --- /dev/null +++ b/contrib/plantuner/expected/plantuner.out @@ -0,0 +1,96 @@ +LOAD 'plantuner'; +SHOW plantuner.disable_index; + plantuner.disable_index +------------------------- + +(1 row) + +CREATE TABLE wow (i int, j int); +CREATE INDEX i_idx ON wow (i); +CREATE INDEX j_idx ON wow (j); +CREATE INDEX i1 ON WOW (i); +CREATE INDEX i2 ON WOW (i); +CREATE INDEX i3 ON WOW (i); +SET enable_seqscan=off; +SELECT * FROM wow; + i | j +---+--- +(0 rows) + +SET plantuner.disable_index="i_idx, j_idx"; +SELECT * FROM wow; + i | j +---+--- +(0 rows) + +SHOW plantuner.disable_index; + plantuner.disable_index +---------------------------- + public.i_idx, public.j_idx +(1 row) + +SET plantuner.disable_index="i_idx, nonexistent, public.j_idx, wow"; +WARNING: 'nonexistent' does not exist +WARNING: 'wow' is not an index +SHOW plantuner.disable_index; + plantuner.disable_index +---------------------------- + public.i_idx, public.j_idx +(1 row) + +SET plantuner.enable_index="i_idx"; +SHOW plantuner.enable_index; + plantuner.enable_index +------------------------ + public.i_idx +(1 row) + +SELECT * FROM wow; + i | j +---+--- +(0 rows) + +--test only index +RESET plantuner.disable_index; +RESET plantuner.enable_index; +SET enable_seqscan=off; +SET enable_bitmapscan=off; +SET enable_indexonlyscan=off; +SET plantuner.only_index="i1"; +SHOW plantuner.only_index; + plantuner.only_index +---------------------- + public.i1 +(1 row) + +EXPLAIN (COSTS OFF) SELECT * FROM wow WHERE i = 0; + QUERY PLAN +---------------------------- + Index Scan using i1 on wow + Index Cond: (i = 0) +(2 rows) + +SET plantuner.disable_index="i1,i2,i3"; +EXPLAIN (COSTS OFF) SELECT * FROM wow WHERE i = 0; + QUERY PLAN +---------------------------- + Index Scan using i1 on wow + Index Cond: (i = 0) +(2 rows) + +SET plantuner.only_index="i2"; +EXPLAIN (COSTS OFF) SELECT * FROM wow WHERE i = 0; + QUERY PLAN +---------------------------- + Index Scan using i2 on wow + Index Cond: (i = 0) +(2 rows) + +RESET plantuner.only_index; +EXPLAIN (COSTS OFF) SELECT * FROM wow WHERE i = 0; + QUERY PLAN +------------------------------- + Index Scan using i_idx on wow + Index Cond: (i = 0) +(2 rows) + diff --git a/contrib/plantuner/plantuner.c b/contrib/plantuner/plantuner.c new file mode 100644 index 00000000000..f87dcea21ff --- /dev/null +++ b/contrib/plantuner/plantuner.c @@ -0,0 +1,554 @@ +/* + * Copyright (c) 2009 Teodor Sigaev <teodor@sigaev.ru> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY CONTRIBUTORS ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <postgres.h> + +#include <fmgr.h> +#include <miscadmin.h> +#include <access/heapam.h> +#include <access/xact.h> +#include <catalog/namespace.h> +#include <catalog/pg_class.h> +#include <nodes/pg_list.h> +#include <optimizer/plancat.h> +#include <storage/bufmgr.h> +#include <utils/builtins.h> +#include <utils/guc.h> +#include <utils/lsyscache.h> +#include <utils/rel.h> +#if PG_VERSION_NUM >= 100000 +#include <utils/regproc.h> +#include <utils/varlena.h> +#endif + +PG_MODULE_MAGIC; + +#if PG_VERSION_NUM >= 130000 +#define heap_open(r, l) table_open(r, l) +#define heap_close(r, l) table_close(r, l) +#endif + +static int nDisabledIndexes = 0; +static Oid *disabledIndexes = NULL; +static char *disableIndexesOutStr = ""; + +static int nEnabledIndexes = 0; +static Oid *enabledIndexes = NULL; +static char *enableIndexesOutStr = ""; + +static int nOnlyIndexes = 0; +static Oid *onlyIndexes = NULL; +static char *onlyIndexesOutStr = ""; + +get_relation_info_hook_type prevHook = NULL; +static bool fix_empty_table = false; + +static bool plantuner_enable_inited = false; +static bool plantuner_only_inited = false; +static bool plantuner_disable_inited = false; + +typedef enum IndexListKind { + EnabledKind, + DisabledKind, + OnlyKind +} IndexListKind; + +static const char * +indexesAssign(const char * newval, bool doit, GucSource source, + IndexListKind kind) +{ + char *rawname; + List *namelist; + ListCell *l; + Oid *newOids = NULL; + int nOids = 0, + i = 0; + + rawname = pstrdup(newval); + + if (!SplitIdentifierString(rawname, ',', &namelist)) + goto cleanup; + + /* + * follow work could be done only in normal processing because of + * accsess to system catalog + */ + if (MyBackendId == InvalidBackendId || !IsUnderPostmaster || + !IsTransactionState()) + { + /* reset init state */ + switch(kind) + { + case EnabledKind: + plantuner_enable_inited = false; + break; + case DisabledKind: + plantuner_disable_inited = false; + break; + case OnlyKind: + plantuner_only_inited = false; + break; + default: + elog(ERROR, "wrong kind"); + } + + return newval; + } + + if (doit) + { + nOids = list_length(namelist); + newOids = malloc(sizeof(Oid) * (nOids+1)); + if (!newOids) + elog(ERROR,"could not allocate %d bytes", + (int)(sizeof(Oid) * (nOids+1))); + } + + switch(kind) + { + case EnabledKind: + plantuner_enable_inited = true; + break; + case DisabledKind: + plantuner_disable_inited = true; + break; + case OnlyKind: + plantuner_only_inited = true; + break; + default: + elog(ERROR, "wrong kind"); + } + + foreach(l, namelist) + { + char *curname = (char *) lfirst(l); +#if PG_VERSION_NUM >= 90200 + Oid indexOid = RangeVarGetRelid( + makeRangeVarFromNameList(stringToQualifiedNameList(curname)), + NoLock, true); +#else + Oid indexOid = RangeVarGetRelid( + makeRangeVarFromNameList(stringToQualifiedNameList(curname)), + true); +#endif + + if (indexOid == InvalidOid) + { +#if PG_VERSION_NUM >= 90100 + if (doit == false) +#endif + elog(WARNING,"'%s' does not exist", curname); + continue; + } + else if ( get_rel_relkind(indexOid) != RELKIND_INDEX ) + { +#if PG_VERSION_NUM >= 90100 + if (doit == false) +#endif + elog(WARNING,"'%s' is not an index", curname); + continue; + } + else if (doit) + { + newOids[i++] = indexOid; + } + } + + if (doit) + { + switch(kind) + { + case EnabledKind: + nEnabledIndexes = i; + if (enabledIndexes) + free(enabledIndexes); + enabledIndexes = newOids; + break; + case DisabledKind: + nDisabledIndexes = i; + if (disabledIndexes) + free(disabledIndexes); + disabledIndexes = newOids; + break; + case OnlyKind: + nOnlyIndexes = i; + if (onlyIndexes) + free(onlyIndexes); + onlyIndexes = newOids; + break; + default: + elog(ERROR, "wrong kind"); + } + } + + pfree(rawname); + list_free(namelist); + + return newval; + +cleanup: + if (newOids) + free(newOids); + pfree(rawname); + list_free(namelist); + return NULL; +} + +static const char * +assignDisabledIndexes(const char * newval, bool doit, GucSource source) +{ + return indexesAssign(newval, doit, source, DisabledKind); +} + +static const char * +assignEnabledIndexes(const char * newval, bool doit, GucSource source) +{ + return indexesAssign(newval, doit, source, EnabledKind); +} + +static const char * +assignOnlyIndexes(const char * newval, bool doit, GucSource source) +{ + return indexesAssign(newval, doit, source, OnlyKind); +} + +static void +lateInit() +{ + if (!plantuner_only_inited) + indexesAssign(onlyIndexesOutStr, true, PGC_S_USER, OnlyKind); + if (!plantuner_enable_inited) + indexesAssign(enableIndexesOutStr, true, PGC_S_USER, EnabledKind); + if (!plantuner_disable_inited) + indexesAssign(disableIndexesOutStr, true, PGC_S_USER, DisabledKind); +} + +#if PG_VERSION_NUM >= 90100 + +static bool +checkOnlyIndexes(char **newval, void **extra, GucSource source) +{ + char *val; + + val = (char*)indexesAssign(*newval, false, source, OnlyKind); + + if (val) + { + *newval = val; + return true; + } + + return false; +} + +static bool +checkDisabledIndexes(char **newval, void **extra, GucSource source) +{ + char *val; + + val = (char*)indexesAssign(*newval, false, source, DisabledKind); + + if (val) + { + *newval = val; + return true; + } + + return false; +} + +static bool +checkEnabledIndexes(char **newval, void **extra, GucSource source) +{ + char *val; + + val = (char*)indexesAssign(*newval, false, source, EnabledKind); + + if (val) + { + *newval = val; + return true; + } + + return false; +} + +static void +assignDisabledIndexesNew(const char *newval, void *extra) +{ + assignDisabledIndexes(newval, true, PGC_S_USER /* doesn't matter */); +} + +static void +assignEnabledIndexesNew(const char *newval, void *extra) +{ + assignEnabledIndexes(newval, true, PGC_S_USER /* doesn't matter */); +} + +static void +assignOnlyIndexesNew(const char *newval, void *extra) +{ + assignOnlyIndexes(newval, true, PGC_S_USER /* doesn't matter */); +} + +#endif + +static void +indexFilter(PlannerInfo *root, Oid relationObjectId, bool inhparent, + RelOptInfo *rel) +{ + int i; + + lateInit(); + + if (nOnlyIndexes > 0) + { + ListCell *l; + +restart1: + foreach(l, rel->indexlist) + { + IndexOptInfo *info = (IndexOptInfo*)lfirst(l); + bool remove = true; + + for(i=0; remove && i<nOnlyIndexes; i++) + if (onlyIndexes[i] == info->indexoid) + remove = false; + + if (remove) + { + rel->indexlist = list_delete_ptr(rel->indexlist, info); + goto restart1; + } + } + + return; + } + + for(i=0; i<nDisabledIndexes; i++) + { + ListCell *l; + + foreach(l, rel->indexlist) + { + IndexOptInfo *info = (IndexOptInfo*)lfirst(l); + + if (disabledIndexes[i] == info->indexoid) + { + int j; + + for(j=0; j<nEnabledIndexes; j++) + if (enabledIndexes[j] == info->indexoid) + break; + + if (j >= nEnabledIndexes) + rel->indexlist = list_delete_ptr(rel->indexlist, info); + + break; + } + } + } +} + +static void +execPlantuner(PlannerInfo *root, Oid relationObjectId, bool inhparent, + RelOptInfo *rel) +{ + Relation relation; + + relation = heap_open(relationObjectId, NoLock); + if (relation->rd_rel->relkind == RELKIND_RELATION) + { + if (fix_empty_table && RelationGetNumberOfBlocks(relation) == 0) + { + /* + * estimate_rel_size() could be too pessimistic for particular + * workload + */ + rel->pages = 1.0; + rel->tuples = 0.0; + } + + indexFilter(root, relationObjectId, inhparent, rel); + } + heap_close(relation, NoLock); + + /* + * Call next hook if it exists + */ + if (prevHook) + prevHook(root, relationObjectId, inhparent, rel); +} + +static const char* +IndexFilterShow(Oid* indexes, int nIndexes) +{ + char *val, *ptr; + int i, + len; + + lateInit(); + + len = 1 /* \0 */ + nIndexes * (2 * NAMEDATALEN + 2 /* ', ' */ + 1 /* . */); + ptr = val = palloc(len); + + *ptr =(char)'\0'; + for(i=0; i<nIndexes; i++) + { + char *relname = get_rel_name(indexes[i]); + Oid nspOid = get_rel_namespace(indexes[i]); + char *nspname = get_namespace_name(nspOid); + + if ( relname == NULL || nspOid == InvalidOid || nspname == NULL ) + continue; + + ptr += snprintf(ptr, len - (ptr - val), "%s%s.%s", + (i==0) ? "" : ", ", + nspname, + relname); + } + + return val; +} + +static const char* +disabledIndexFilterShow(void) +{ + return IndexFilterShow(disabledIndexes, nDisabledIndexes); +} + +static const char* +enabledIndexFilterShow(void) +{ + return IndexFilterShow(enabledIndexes, nEnabledIndexes); +} + +static const char* +onlyIndexFilterShow(void) +{ + return IndexFilterShow(onlyIndexes, nOnlyIndexes); +} + +void _PG_init(void); +void +_PG_init(void) +{ + DefineCustomStringVariable( + "plantuner.forbid_index", + "List of forbidden indexes (deprecated)", + "Listed indexes will not be used in queries (deprecated, use plantuner.disable_index)", + &disableIndexesOutStr, + "", + PGC_USERSET, + 0, +#if PG_VERSION_NUM >= 90100 + checkDisabledIndexes, + assignDisabledIndexesNew, +#else + assignDisabledIndexes, +#endif + disabledIndexFilterShow + ); + + DefineCustomStringVariable( + "plantuner.disable_index", + "List of disabled indexes", + "Listed indexes will not be used in queries", + &disableIndexesOutStr, + "", + PGC_USERSET, + 0, +#if PG_VERSION_NUM >= 90100 + checkDisabledIndexes, + assignDisabledIndexesNew, +#else + assignDisabledIndexes, +#endif + disabledIndexFilterShow + ); + + DefineCustomStringVariable( + "plantuner.enable_index", + "List of enabled indexes (overload plantuner.disable_index)", + "Listed indexes which could be used in queries even they are listed in plantuner.disable_index", + &enableIndexesOutStr, + "", + PGC_USERSET, + 0, +#if PG_VERSION_NUM >= 90100 + checkEnabledIndexes, + assignEnabledIndexesNew, +#else + assignEnabledIndexes, +#endif + enabledIndexFilterShow + ); + + DefineCustomStringVariable( + "plantuner.only_index", + "List of explicitly enabled indexes (overload plantuner.disable_index and plantuner.enable_index)", + "Only indexes in this list are allowed", + &onlyIndexesOutStr, + "", + PGC_USERSET, + 0, +#if PG_VERSION_NUM >= 90100 + checkOnlyIndexes, + assignOnlyIndexesNew, +#else + assignOnlyIndexes, +#endif + onlyIndexFilterShow + ); + + DefineCustomBoolVariable( + "plantuner.fix_empty_table", + "Sets to zero estimations for empty tables", + "Sets to zero estimations for empty or newly created tables", + &fix_empty_table, +#if PG_VERSION_NUM >= 80400 + fix_empty_table, +#endif + PGC_USERSET, +#if PG_VERSION_NUM >= 80400 + GUC_NOT_IN_SAMPLE, +#if PG_VERSION_NUM >= 90100 + NULL, +#endif +#endif + NULL, + NULL + ); + + if (get_relation_info_hook != execPlantuner ) + { + prevHook = get_relation_info_hook; + get_relation_info_hook = execPlantuner; + } +} diff --git a/contrib/plantuner/sql/plantuner.sql b/contrib/plantuner/sql/plantuner.sql new file mode 100644 index 00000000000..ddd6fcc94f1 --- /dev/null +++ b/contrib/plantuner/sql/plantuner.sql @@ -0,0 +1,51 @@ +LOAD 'plantuner'; + +SHOW plantuner.disable_index; + +CREATE TABLE wow (i int, j int); +CREATE INDEX i_idx ON wow (i); +CREATE INDEX j_idx ON wow (j); +CREATE INDEX i1 ON WOW (i); +CREATE INDEX i2 ON WOW (i); +CREATE INDEX i3 ON WOW (i); + +SET enable_seqscan=off; + +SELECT * FROM wow; + +SET plantuner.disable_index="i_idx, j_idx"; + +SELECT * FROM wow; + +SHOW plantuner.disable_index; + +SET plantuner.disable_index="i_idx, nonexistent, public.j_idx, wow"; + +SHOW plantuner.disable_index; + +SET plantuner.enable_index="i_idx"; + +SHOW plantuner.enable_index; + +SELECT * FROM wow; +--test only index +RESET plantuner.disable_index; +RESET plantuner.enable_index; + +SET enable_seqscan=off; +SET enable_bitmapscan=off; +SET enable_indexonlyscan=off; + +SET plantuner.only_index="i1"; +SHOW plantuner.only_index; + +EXPLAIN (COSTS OFF) SELECT * FROM wow WHERE i = 0; + +SET plantuner.disable_index="i1,i2,i3"; +EXPLAIN (COSTS OFF) SELECT * FROM wow WHERE i = 0; + +SET plantuner.only_index="i2"; +EXPLAIN (COSTS OFF) SELECT * FROM wow WHERE i = 0; + +RESET plantuner.only_index; +EXPLAIN (COSTS OFF) SELECT * FROM wow WHERE i = 0; diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index 336e24d538e..f142b5f13ae 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -1094,13 +1094,15 @@ ANALYZE ft5; -- join two tables EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; - QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - Foreign Scan + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + Limit Output: t1.c1, t2.c1, t1.c3 - Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) - Remote SQL: SELECT r1."C 1", r2."C 1", r1.c3 FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 100::bigint -(4 rows) + -> Foreign Scan + Output: t1.c1, t2.c1, t1.c3 + Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) + Remote SQL: SELECT r1."C 1", r2."C 1", r1.c3 FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST +(6 rows) SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; c1 | c1 @@ -1120,13 +1122,15 @@ SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t -- join three tables EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c2, t3.c3 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) JOIN ft4 t3 ON (t3.c1 = t1.c1) ORDER BY t1.c3, t1.c1 OFFSET 10 LIMIT 10; - QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - Foreign Scan + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Limit Output: t1.c1, t2.c2, t3.c3, t1.c3 - Relations: ((public.ft1 t1) INNER JOIN (public.ft2 t2)) INNER JOIN (public.ft4 t3) - Remote SQL: SELECT r1."C 1", r2.c2, r4.c3, r1.c3 FROM (("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) INNER JOIN "S 1"."T 3" r4 ON (((r1."C 1" = r4.c1)))) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint -(4 rows) + -> Foreign Scan + Output: t1.c1, t2.c2, t3.c3, t1.c3 + Relations: ((public.ft1 t1) INNER JOIN (public.ft2 t2)) INNER JOIN (public.ft4 t3) + Remote SQL: SELECT r1."C 1", r2.c2, r4.c3, r1.c3 FROM (("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) INNER JOIN "S 1"."T 3" r4 ON (((r1."C 1" = r4.c1)))) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST +(6 rows) SELECT t1.c1, t2.c2, t3.c3 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) JOIN ft4 t3 ON (t3.c1 = t1.c1) ORDER BY t1.c3, t1.c1 OFFSET 10 LIMIT 10; c1 | c2 | c3 @@ -1733,13 +1737,33 @@ ALTER SERVER loopback OPTIONS (ADD extensions 'postgres_fdw'); -- tests whole-row reference for row marks EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR UPDATE OF t1; - QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - Foreign Scan + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Limit Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* - Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) - Remote SQL: SELECT r1."C 1", r2."C 1", r1.c3, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 100::bigint FOR UPDATE OF r1 -(4 rows) + -> LockRows + Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* + -> Foreign Scan + Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* + Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) + Remote SQL: SELECT r1."C 1", r2."C 1", r1.c3, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST FOR UPDATE OF r1 + -> Result + Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* + -> Sort + Output: t1.c1, t1.c3, t1.*, t2.c1, t2.* + Sort Key: t1.c3, t1.c1 + -> Hash Join + Output: t1.c1, t1.c3, t1.*, t2.c1, t2.* + Hash Cond: (t1.c1 = t2.c1) + -> Foreign Scan on public.ft1 t1 + Output: t1.c1, t1.c3, t1.* + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR UPDATE + -> Hash + Output: t2.c1, t2.* + -> Foreign Scan on public.ft2 t2 + Output: t2.c1, t2.* + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" +(24 rows) SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR UPDATE OF t1; c1 | c1 @@ -1758,13 +1782,33 @@ SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR UPDATE; - QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - Foreign Scan + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Limit Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* - Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) - Remote SQL: SELECT r1."C 1", r2."C 1", r1.c3, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 100::bigint FOR UPDATE OF r1 FOR UPDATE OF r2 -(4 rows) + -> LockRows + Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* + -> Foreign Scan + Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* + Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) + Remote SQL: SELECT r1."C 1", r2."C 1", r1.c3, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST FOR UPDATE OF r1 FOR UPDATE OF r2 + -> Result + Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* + -> Sort + Output: t1.c1, t1.c3, t1.*, t2.c1, t2.* + Sort Key: t1.c3, t1.c1 + -> Hash Join + Output: t1.c1, t1.c3, t1.*, t2.c1, t2.* + Hash Cond: (t1.c1 = t2.c1) + -> Foreign Scan on public.ft1 t1 + Output: t1.c1, t1.c3, t1.* + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR UPDATE + -> Hash + Output: t2.c1, t2.* + -> Foreign Scan on public.ft2 t2 + Output: t2.c1, t2.* + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR UPDATE +(24 rows) SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR UPDATE; c1 | c1 @@ -1784,13 +1828,33 @@ SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t -- join two tables with FOR SHARE clause EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR SHARE OF t1; - QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - Foreign Scan + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Limit Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* - Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) - Remote SQL: SELECT r1."C 1", r2."C 1", r1.c3, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 100::bigint FOR SHARE OF r1 -(4 rows) + -> LockRows + Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* + -> Foreign Scan + Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* + Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) + Remote SQL: SELECT r1."C 1", r2."C 1", r1.c3, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST FOR SHARE OF r1 + -> Result + Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* + -> Sort + Output: t1.c1, t1.c3, t1.*, t2.c1, t2.* + Sort Key: t1.c3, t1.c1 + -> Hash Join + Output: t1.c1, t1.c3, t1.*, t2.c1, t2.* + Hash Cond: (t1.c1 = t2.c1) + -> Foreign Scan on public.ft1 t1 + Output: t1.c1, t1.c3, t1.* + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR SHARE + -> Hash + Output: t2.c1, t2.* + -> Foreign Scan on public.ft2 t2 + Output: t2.c1, t2.* + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" +(24 rows) SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR SHARE OF t1; c1 | c1 @@ -1809,13 +1873,33 @@ SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR SHARE; - QUERY PLAN --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - Foreign Scan + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Limit Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* - Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) - Remote SQL: SELECT r1."C 1", r2."C 1", r1.c3, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 100::bigint FOR SHARE OF r1 FOR SHARE OF r2 -(4 rows) + -> LockRows + Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* + -> Foreign Scan + Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* + Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) + Remote SQL: SELECT r1."C 1", r2."C 1", r1.c3, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST FOR SHARE OF r1 FOR SHARE OF r2 + -> Result + Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* + -> Sort + Output: t1.c1, t1.c3, t1.*, t2.c1, t2.* + Sort Key: t1.c3, t1.c1 + -> Hash Join + Output: t1.c1, t1.c3, t1.*, t2.c1, t2.* + Hash Cond: (t1.c1 = t2.c1) + -> Foreign Scan on public.ft1 t1 + Output: t1.c1, t1.c3, t1.* + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR SHARE + -> Hash + Output: t2.c1, t2.* + -> Foreign Scan on public.ft2 t2 + Output: t2.c1, t2.* + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR SHARE +(24 rows) SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR SHARE; c1 | c1 @@ -1869,13 +1953,15 @@ WITH t (c1_1, c1_3, c2_1) AS MATERIALIZED (SELECT t1.c1, t1.c3, t2.c1 FROM ft1 t -- ctid with whole-row reference EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.ctid, t1, t2, t1.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; - QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - Foreign Scan + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Limit Output: t1.ctid, t1.*, t2.*, t1.c1, t1.c3 - Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) - Remote SQL: SELECT r1.ctid, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END, r1."C 1", r1.c3 FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 100::bigint -(4 rows) + -> Foreign Scan + Output: t1.ctid, t1.*, t2.*, t1.c1, t1.c3 + Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) + Remote SQL: SELECT r1.ctid, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END, r1."C 1", r1.c3 FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST +(6 rows) -- SEMI JOIN, not pushed down EXPLAIN (VERBOSE, COSTS OFF) @@ -1946,13 +2032,15 @@ SELECT t1.c1 FROM ft1 t1 WHERE NOT EXISTS (SELECT 1 FROM ft2 t2 WHERE t1.c1 = t2 -- CROSS JOIN can be pushed down EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c1 FROM ft1 t1 CROSS JOIN ft2 t2 ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10; - QUERY PLAN -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - Foreign Scan + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Limit Output: t1.c1, t2.c1 - Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) - Remote SQL: SELECT r1."C 1", r2."C 1" FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (TRUE)) ORDER BY r1."C 1" ASC NULLS LAST, r2."C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 100::bigint -(4 rows) + -> Foreign Scan + Output: t1.c1, t2.c1 + Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) + Remote SQL: SELECT r1."C 1", r2."C 1" FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (TRUE)) ORDER BY r1."C 1" ASC NULLS LAST, r2."C 1" ASC NULLS LAST +(6 rows) SELECT t1.c1, t2.c1 FROM ft1 t1 CROSS JOIN ft2 t2 ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10; c1 | c1 @@ -2318,32 +2406,35 @@ SELECT * FROM ft1, ft2, ft4, ft5, local_tbl WHERE ft1.c1 = ft2.c1 AND ft1.c2 = f -> Merge Join Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.*, ft4.c1, ft4.c2, ft4.c3, ft4.*, ft5.c1, ft5.c2, ft5.c3, ft5.* Merge Cond: (ft1.c2 = ft5.c1) - -> Merge Join + -> Sort Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.*, ft4.c1, ft4.c2, ft4.c3, ft4.* - Merge Cond: (ft1.c2 = ft4.c1) - -> Sort - Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.* - Sort Key: ft1.c2 - -> Merge Join + Sort Key: ft1.c2 + -> Merge Join + Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.*, ft4.c1, ft4.c2, ft4.c3, ft4.* + Merge Cond: (ft1.c2 = ft4.c1) + -> Sort Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.* - Merge Cond: (ft1.c1 = ft2.c1) - -> Sort - Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.* - Sort Key: ft1.c1 - -> Foreign Scan on public.ft1 + Sort Key: ft1.c2 + -> Merge Join + Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.* + Merge Cond: (ft1.c1 = ft2.c1) + -> Sort Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.* - Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < 100)) FOR UPDATE - -> Materialize - Output: ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.* - -> Foreign Scan on public.ft2 + Sort Key: ft1.c1 + -> Foreign Scan on public.ft1 + Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.* + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < 100)) FOR UPDATE + -> Materialize Output: ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.* - Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < 100)) ORDER BY "C 1" ASC NULLS LAST FOR UPDATE - -> Sort - Output: ft4.c1, ft4.c2, ft4.c3, ft4.* - Sort Key: ft4.c1 - -> Foreign Scan on public.ft4 + -> Foreign Scan on public.ft2 + Output: ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.* + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < 100)) ORDER BY "C 1" ASC NULLS LAST FOR UPDATE + -> Sort Output: ft4.c1, ft4.c2, ft4.c3, ft4.* - Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 3" FOR UPDATE + Sort Key: ft4.c1 + -> Foreign Scan on public.ft4 + Output: ft4.c1, ft4.c2, ft4.c3, ft4.* + Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 3" FOR UPDATE -> Sort Output: ft5.c1, ft5.c2, ft5.c3, ft5.* Sort Key: ft5.c1 @@ -2352,7 +2443,7 @@ SELECT * FROM ft1, ft2, ft4, ft5, local_tbl WHERE ft1.c1 = ft2.c1 AND ft1.c2 = f Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 4" FOR UPDATE -> Index Scan using local_tbl_pkey on public.local_tbl Output: local_tbl.c1, local_tbl.c2, local_tbl.c3, local_tbl.ctid -(47 rows) +(50 rows) SELECT * FROM ft1, ft2, ft4, ft5, local_tbl WHERE ft1.c1 = ft2.c1 AND ft1.c2 = ft4.c1 AND ft1.c2 = ft5.c1 AND ft1.c2 = local_tbl.c1 AND ft1.c1 < 100 AND ft2.c1 < 100 FOR UPDATE; @@ -2616,13 +2707,15 @@ select count(c6), sum(c1), avg(c1), min(c2), max(c1), stddev(c2), sum(c1) * (ran explain (verbose, costs off) select count(c6), sum(c1), avg(c1), min(c2), max(c1), stddev(c2), sum(c1) * (random() <= 1)::int as sum2 from ft1 where c2 < 5 group by c2 order by 1, 2 limit 1; - QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - Foreign Scan - Output: (count(c6)), (sum(c1)), (avg(c1)), (min(c2)), (max(c1)), (stddev(c2)), ((sum(c1)) * ((random() <= '1'::double precision))::integer), c2 - Relations: Aggregate on (public.ft1) - Remote SQL: SELECT count(c6), sum("C 1"), avg("C 1"), min(c2), max("C 1"), stddev(c2), c2 FROM "S 1"."T 1" WHERE ((c2 < 5)) GROUP BY 7 ORDER BY count(c6) ASC NULLS LAST, sum("C 1") ASC NULLS LAST LIMIT 1::bigint -(4 rows) + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Limit + Output: (count(c6)), (sum(c1)), (avg(c1)), (min(c2)), (max(c1)), (stddev(c2)), (((sum(c1)) * ((random() <= '1'::double precision))::integer)), c2 + -> Foreign Scan + Output: (count(c6)), (sum(c1)), (avg(c1)), (min(c2)), (max(c1)), (stddev(c2)), ((sum(c1)) * ((random() <= '1'::double precision))::integer), c2 + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT count(c6), sum("C 1"), avg("C 1"), min(c2), max("C 1"), stddev(c2), c2 FROM "S 1"."T 1" WHERE ((c2 < 5)) GROUP BY 7 ORDER BY count(c6) ASC NULLS LAST, sum("C 1") ASC NULLS LAST +(6 rows) select count(c6), sum(c1), avg(c1), min(c2), max(c1), stddev(c2), sum(c1) * (random() <= 1)::int as sum2 from ft1 where c2 < 5 group by c2 order by 1, 2 limit 1; count | sum | avg | min | max | stddev | sum2 @@ -2761,16 +2854,13 @@ select c2 * (random() <= 1)::int as c2 from ft2 group by c2 * (random() <= 1)::i -- GROUP BY clause in various forms, cardinal, alias and constant expression explain (verbose, costs off) select count(c2) w, c2 x, 5 y, 7.0 z from ft1 group by 2, y, 9.0::int order by 2; - QUERY PLAN ---------------------------------------------------------------------------------------- - Sort + QUERY PLAN +------------------------------------------------------------------------------------------------------------ + Foreign Scan Output: (count(c2)), c2, 5, 7.0, 9 - Sort Key: ft1.c2 - -> Foreign Scan - Output: (count(c2)), c2, 5, 7.0, 9 - Relations: Aggregate on (public.ft1) - Remote SQL: SELECT count(c2), c2, 5, 7.0, 9 FROM "S 1"."T 1" GROUP BY 2, 3, 5 -(7 rows) + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT count(c2), c2, 5, 7.0, 9 FROM "S 1"."T 1" GROUP BY 2, 3, 5 ORDER BY c2 ASC NULLS LAST +(4 rows) select count(c2) w, c2 x, 5 y, 7.0 z from ft1 group by 2, y, 9.0::int order by 2; w | x | y | z diff --git a/contrib/test_decoding/expected/time.out b/contrib/test_decoding/expected/time.out index 3b06849d697..166ef85c519 100644 --- a/contrib/test_decoding/expected/time.out +++ b/contrib/test_decoding/expected/time.out @@ -1,3 +1,4 @@ +SET standard_conforming_strings=on; SET synchronous_commit = on; CREATE TABLE test_time(data text); -- remember the current time diff --git a/contrib/test_decoding/expected/toast.out b/contrib/test_decoding/expected/toast.out index a757e7dc8d5..8fe72e1dbd2 100644 --- a/contrib/test_decoding/expected/toast.out +++ b/contrib/test_decoding/expected/toast.out @@ -1,4 +1,5 @@ -- predictability +SET standard_conforming_strings=on; SET synchronous_commit = on; DROP TABLE IF EXISTS xpto; NOTICE: table "xpto" does not exist, skipping diff --git a/contrib/test_decoding/sql/time.sql b/contrib/test_decoding/sql/time.sql index a47c9731f34..1f04eb59893 100644 --- a/contrib/test_decoding/sql/time.sql +++ b/contrib/test_decoding/sql/time.sql @@ -1,3 +1,4 @@ +SET standard_conforming_strings=on; SET synchronous_commit = on; CREATE TABLE test_time(data text); diff --git a/contrib/test_decoding/sql/toast.sql b/contrib/test_decoding/sql/toast.sql index d1c560a174d..44b39e692c0 100644 --- a/contrib/test_decoding/sql/toast.sql +++ b/contrib/test_decoding/sql/toast.sql @@ -1,4 +1,5 @@ -- predictability +SET standard_conforming_strings=on; SET synchronous_commit = on; DROP TABLE IF EXISTS xpto; diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c index 69cadbcede9..3486f0e1fce 100644 --- a/src/backend/access/heap/hio.c +++ b/src/backend/access/heap/hio.c @@ -563,7 +563,8 @@ loop: else if (!ConditionalLockRelationForExtension(relation, ExclusiveLock)) { /* Couldn't get the lock immediately; wait for it. */ - LockRelationForExtension(relation, ExclusiveLock); + LockRelationForExtension(relation, ShareLock); + UnlockRelationForExtension(relation, ShareLock); /* * Check if some other backend has extended a block for us while @@ -575,13 +576,20 @@ loop: * If some other waiter has already extended the relation, we * don't need to do so; just use the existing freespace. */ + if (targetBlock != InvalidBlockNumber) + goto loop; + + /* Time to bulk-extend. */ + LockRelationForExtension(relation, ExclusiveLock); + + /* last chance */ + targetBlock = GetPageWithFreeSpace(relation, len + saveFreeSpace); if (targetBlock != InvalidBlockNumber) { UnlockRelationForExtension(relation, ExclusiveLock); goto loop; } - /* Time to bulk-extend. */ RelationAddExtraBlocks(relation, bistate); } } diff --git a/src/backend/access/heap/visibilitymap.c b/src/backend/access/heap/visibilitymap.c index 6a14039ea7b..567e8803827 100644 --- a/src/backend/access/heap/visibilitymap.c +++ b/src/backend/access/heap/visibilitymap.c @@ -667,6 +667,7 @@ vm_extend(Relation rel, BlockNumber vm_nblocks) * to keep checking for creation or extension of the file, which happens * infrequently. */ + if (!RELATION_IS_LOCAL(rel)) CacheInvalidateSmgr(reln->smgr_rnode); /* Update local cache with the up-to-date size */ diff --git a/src/backend/access/transam/twophase.c b/src/backend/access/transam/twophase.c index d375b1012be..f8272c2f0d0 100644 --- a/src/backend/access/transam/twophase.c +++ b/src/backend/access/transam/twophase.c @@ -484,7 +484,7 @@ MarkAsPreparingGuts(GlobalTransaction gxact, TransactionId xid, const char *gid, proc->roleId = owner; proc->tempNamespaceId = InvalidOid; proc->isBackgroundWorker = false; - proc->lwWaiting = false; + proc->lwWaiting = LW_WS_NOT_WAITING; proc->lwWaitMode = 0; proc->waitLock = NULL; proc->waitProcLock = NULL; diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c index 11bc277ba32..d0c126fb174 100644 --- a/src/backend/catalog/dependency.c +++ b/src/backend/catalog/dependency.c @@ -317,6 +317,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 f5f88644455..9175dca9446 100644 --- a/src/backend/catalog/index.c +++ b/src/backend/catalog/index.c @@ -58,6 +58,7 @@ #include "commands/progress.h" #include "commands/tablecmds.h" #include "commands/trigger.h" +#include "commands/typecmds.h" #include "executor/executor.h" #include "miscadmin.h" #include "nodes/makefuncs.h" @@ -105,7 +106,7 @@ static TupleDesc ConstructTupleDescriptor(Relation heapRelation, List *indexColNames, Oid accessMethodObjectId, Oid *collationObjectId, - Oid *classObjectId); + Oid *classObjectId); static void InitializeAttributeOids(Relation indexRelation, int numatts, Oid indexoid); static void AppendAttributeTuples(Relation indexRelation, int numatts, @@ -133,7 +134,7 @@ static void SetReindexProcessing(Oid heapOid, Oid indexOid); static void ResetReindexProcessing(void); static void SetReindexPending(List *indexes); static void RemoveReindexPending(Oid indexOid); - +static void IndexTypeCreate(Relation indexRelation); /* * relationHasPrimaryKey @@ -643,6 +644,110 @@ UpdateIndexRelation(Oid indexoid, heap_freetuple(tuple); } +/* + * We only need to create reltype for multicolumn user-defined + * B-tree indexes that don't have a reltype yet. + */ +#define INDEX_NEEDS_RELTYPE(indexRelation, indexInfo, accessMethodOid) ( \ + !IsSystemRelation(indexRelation) \ + && indexInfo->ii_NumIndexKeyAttrs > 1 \ + && accessMethodOid == BTREE_AM_OID \ + && indexRelation->rd_rel->reltype == InvalidOid \ + && (!IsBinaryUpgrade || binary_upgrade_next_pg_type_oid != InvalidOid)) + +/* + * IndexTypeCreate + * + * Create type for specified index. + */ +void +IndexTypeCreate(Relation indexRelation) +{ + Oid ownerId = GetUserId(); + Oid namespaceId = RelationGetNamespace(indexRelation); + Oid new_array_oid = AssignTypeArrayOid(); + ObjectAddress new_type_addr; + char* relarrayname; + + /* Index must not have a reltype yet */ + Assert(indexRelation->rd_rel->reltype == InvalidOid); + + /* + * Build compound type for compound index to be able to use it in statistic. + * We need to collect statistic for compound indexes to be able to better + * predict selectivity of multicolumn joins. + */ + new_type_addr = TypeCreate(InvalidOid, + RelationGetRelationName(indexRelation), + namespaceId, + RelationGetRelid(indexRelation), + RELKIND_INDEX, + ownerId, /* owner's ID */ + -1, /* internal size (varlena) */ + TYPTYPE_COMPOSITE, /* type-type (composite) */ + TYPCATEGORY_COMPOSITE, /* type-category (ditto) */ + false, /* composite types are never preferred */ + DEFAULT_TYPDELIM, /* default array delimiter */ + F_RECORD_IN, /* input procedure */ + F_RECORD_OUT, /* output procedure */ + F_RECORD_RECV, /* receive procedure */ + F_RECORD_SEND, /* send procedure */ + InvalidOid, /* typmodin procedure - none */ + InvalidOid, /* typmodout procedure - none */ + InvalidOid, /* analyze procedure - default */ + InvalidOid, /* array element type - irrelevant */ + false, /* this is not an array type */ + new_array_oid, /* array type if any */ + InvalidOid, /* domain base type - irrelevant */ + NULL, /* default value - none */ + NULL, /* default binary representation */ + false, /* passed by reference */ + 'd', /* alignment - must be the largest! */ + 'x', /* fully TOASTable */ + -1, /* typmod */ + 0, /* array dimensions for typBaseType */ + false, /* Type NOT NULL */ + InvalidOid); /* rowtypes never have a collation */ + + indexRelation->rd_rel->reltype = new_type_addr.objectId; + + relarrayname = makeArrayTypeName(RelationGetRelationName(indexRelation), + namespaceId); + + TypeCreate(new_array_oid, /* force the type's OID to this */ + relarrayname, /* Array type name */ + namespaceId, /* Same namespace as parent */ + InvalidOid, /* Not composite, no relationOid */ + 0, /* relkind, also N/A here */ + ownerId, /* owner's ID */ + -1, /* Internal size (varlena) */ + TYPTYPE_BASE, /* Not composite - typelem is */ + TYPCATEGORY_ARRAY, /* type-category (array) */ + false, /* array types are never preferred */ + DEFAULT_TYPDELIM, /* default array delimiter */ + F_ARRAY_IN, /* array input proc */ + F_ARRAY_OUT, /* array output proc */ + F_ARRAY_RECV, /* array recv (bin) proc */ + F_ARRAY_SEND, /* array send (bin) proc */ + InvalidOid, /* typmodin procedure - none */ + InvalidOid, /* typmodout procedure - none */ + F_ARRAY_TYPANALYZE, /* array analyze procedure */ + indexRelation->rd_rel->reltype, /* array element type - the rowtype */ + true, /* yes, this is an array type */ + InvalidOid, /* this has no array type */ + InvalidOid, /* domain base type - irrelevant */ + NULL, /* default value - none */ + NULL, /* default binary representation */ + false, /* passed by reference */ + 'd', /* alignment - must be the largest! */ + 'x', /* fully TOASTable */ + -1, /* typmod */ + 0, /* array dimensions for typBaseType */ + false, /* Type NOT NULL */ + InvalidOid); /* rowtypes never have a collation */ + + pfree(relarrayname); +} /* * index_create @@ -726,6 +831,7 @@ index_create(Relation heapRelation, bool invalid = (flags & INDEX_CREATE_INVALID) != 0; bool concurrent = (flags & INDEX_CREATE_CONCURRENT) != 0; bool partitioned = (flags & INDEX_CREATE_PARTITIONED) != 0; + bool withoutType = (flags & INDEX_CREATE_WITHOUT_TYPE) != 0; char relkind; TransactionId relfrozenxid; MultiXactId relminmxid; @@ -942,6 +1048,11 @@ index_create(Relation heapRelation, Assert(relminmxid == InvalidMultiXactId); Assert(indexRelationId == RelationGetRelid(indexRelation)); + /* Create a reltype for index if it is needed */ + if (withoutType == false && INDEX_NEEDS_RELTYPE(indexRelation, indexInfo, accessMethodObjectId) + && !is_internal) + IndexTypeCreate(indexRelation); + /* * Obtain exclusive lock on it. Although no other transactions can see it * until we commit, this prevents deadlock-risk complaints from lock @@ -1394,7 +1505,7 @@ index_concurrently_create_copy(Relation heapRelation, Oid oldIndexId, const char indclass->values, indcoloptions->values, optionDatum, - INDEX_CREATE_SKIP_BUILD | INDEX_CREATE_CONCURRENT, + INDEX_CREATE_SKIP_BUILD | INDEX_CREATE_CONCURRENT | INDEX_CREATE_WITHOUT_TYPE, 0, true, /* allow table to be a system catalog? */ false, /* is_internal? */ @@ -1535,6 +1646,32 @@ index_concurrently_swap(Oid newIndexId, Oid oldIndexId, const char *oldName) newClassForm->relispartition = oldClassForm->relispartition; oldClassForm->relispartition = isPartition; + /* copy index type to new index */ + newClassForm->reltype = oldClassForm->reltype; + + if (OidIsValid(oldClassForm->reltype)) + { + Relation pg_type; + HeapTuple typeTuple; + Form_pg_type typeForm; + + pg_type = table_open(TypeRelationId, RowExclusiveLock); + + typeTuple = SearchSysCacheCopy1(TYPEOID, + ObjectIdGetDatum(oldClassForm->reltype)); + if (!HeapTupleIsValid(typeTuple)) + elog(ERROR, "could not find tuple for type %u", oldClassForm->reltype); + + typeForm = (Form_pg_type) GETSTRUCT(typeTuple); + + typeForm->typrelid = newIndexId; + + CatalogTupleUpdate(pg_type, &typeTuple->t_self, typeTuple); + + heap_freetuple(typeTuple); + table_close(pg_type, RowExclusiveLock); + } + CatalogTupleUpdate(pg_class, &oldClassTuple->t_self, oldClassTuple); CatalogTupleUpdate(pg_class, &newClassTuple->t_self, newClassTuple); @@ -1723,8 +1860,9 @@ index_concurrently_swap(Oid newIndexId, Oid oldIndexId, const char *oldName) * vice-versa. Note that a call to CommandCounterIncrement() would cause * duplicate entries in pg_depend, so this should not be done. */ - changeDependenciesOf(RelationRelationId, newIndexId, oldIndexId); - changeDependenciesOn(RelationRelationId, newIndexId, oldIndexId); + //changeDependenciesOf(RelationRelationId, newIndexId, oldIndexId); + //changeDependenciesOn(RelationRelationId, newIndexId, oldIndexId); + deleteDependencyRecordsFor(RelationRelationId, newIndexId, false); changeDependenciesOf(RelationRelationId, oldIndexId, newIndexId); changeDependenciesOn(RelationRelationId, oldIndexId, newIndexId); @@ -2132,6 +2270,7 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode) Relation indexRelation; HeapTuple tuple; bool hasexprs; + bool remove_statistics; LockRelId heaprelid, indexrelid; LOCKTAG heaplocktag; @@ -2208,24 +2347,6 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode) */ if (concurrent) { - /* - * We must commit our transaction in order to make the first pg_index - * state update visible to other sessions. If the DROP machinery has - * already performed any other actions (removal of other objects, - * pg_depend entries, etc), the commit would make those actions - * permanent, which would leave us with inconsistent catalog state if - * we fail partway through the following sequence. Since DROP INDEX - * CONCURRENTLY is restricted to dropping just one index that has no - * dependencies, we should get here before anything's been done --- - * but let's check that to be sure. We can verify that the current - * transaction has not executed any transactional updates by checking - * that no XID has been assigned. - */ - if (GetTopTransactionIdIfAny() != InvalidTransactionId) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("DROP INDEX CONCURRENTLY must be first action in transaction"))); - /* * Mark index invalid by updating its pg_index entry */ @@ -2322,6 +2443,16 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode) if (userIndexRelation->rd_rel->relkind != RELKIND_PARTITIONED_INDEX) RelationDropStorage(userIndexRelation); + /* + * We might have stored multicolumn statistics for btree indexes. They are + * created only for non-system and non-TOAST indexes, so check only for such + * such indexes. + */ + remove_statistics = + IndexRelationGetNumberOfKeyAttributes(userIndexRelation) > 1 && + userIndexRelation->rd_rel->relam == BTREE_AM_OID && + !IsSystemRelation(userIndexRelation); + /* * Close and flush the index's relcache entry, to ensure relcache doesn't * try to rebuild it while we're deleting catalog entries. We keep the @@ -2349,10 +2480,10 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode) table_close(indexRelation, RowExclusiveLock); /* - * if it has any expression columns, we might have stored statistics about - * them. + * if it has any expression columns or whole index stat, we might have + * stored statistics about them. */ - if (hasexprs) + if (hasexprs || remove_statistics) RemoveStatistics(indexId, 0); /* @@ -2867,6 +2998,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); @@ -3648,6 +3787,10 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence, /* Create a new physical relation for the index */ RelationSetNewRelfilenode(iRel, persistence); + /* Create a reltype for index if it is needed */ + if (INDEX_NEEDS_RELTYPE(iRel, indexInfo, iRel->rd_rel->relam)) + IndexTypeCreate(iRel); + /* Initialize the index and rebuild */ /* Note: we do not need to re-establish pkey setting */ index_build(heapRelation, iRel, indexInfo, true, true); diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c index 01b797e32dd..31030949bf4 100644 --- a/src/backend/commands/analyze.c +++ b/src/backend/commands/analyze.c @@ -40,8 +40,11 @@ #include "commands/vacuum.h" #include "executor/executor.h" #include "foreign/fdwapi.h" +#include "funcapi.h" #include "miscadmin.h" #include "nodes/nodeFuncs.h" +#include "nodes/makefuncs.h" +#include "nodes/pg_list.h" #include "parser/parse_oper.h" #include "parser/parse_relation.h" #include "pgstat.h" @@ -65,6 +68,7 @@ #include "utils/sortsupport.h" #include "utils/syscache.h" #include "utils/timestamp.h" +#include "utils/typcache.h" /* Per-index data for ANALYZE */ @@ -74,6 +78,7 @@ typedef struct AnlIndexData double tupleFract; /* fraction of rows for partial index */ VacAttrStats **vacattrstats; /* index attrs to analyze */ int attr_cnt; + bool multicolumn; /* Collect compound row statistic for multicolumn index */ } AnlIndexData; @@ -471,6 +476,21 @@ do_analyze_rel(Relation onerel, VacuumParams *params, } thisdata->attr_cnt = tcnt; } + else if (indexInfo->ii_NumIndexAttrs > 1 && va_cols == NIL && + Irel[ind]->rd_rel->reltype != InvalidOid) + { + /* Collect statistic for multicolumn index for better predicting selectivity of multicolumn joins */ + RowExpr* row = makeNode(RowExpr); + row->row_typeid = Irel[ind]->rd_rel->reltype; + row->row_format = COERCE_EXPLICIT_CAST; + row->location = -1; + row->colnames = NULL; + thisdata->vacattrstats = (VacAttrStats **)palloc(sizeof(VacAttrStats *)); + thisdata->vacattrstats[0] = examine_attribute(Irel[ind], 1, (Node*)row); + thisdata->vacattrstats[0]->tupDesc = lookup_type_cache(row->row_typeid, TYPECACHE_TUPDESC)->tupDesc; + thisdata->attr_cnt = 1; + thisdata->multicolumn = true; + } } } @@ -840,28 +860,41 @@ compute_index_stats(Relation onerel, double totalrows, values, isnull); - /* - * Save just the columns we care about. We copy the values - * into ind_context from the estate's per-tuple context. - */ - for (i = 0; i < attr_cnt; i++) + if (thisdata->multicolumn) { - VacAttrStats *stats = thisdata->vacattrstats[i]; - int attnum = stats->attr->attnum; - - if (isnull[attnum - 1]) - { - exprvals[tcnt] = (Datum) 0; - exprnulls[tcnt] = true; - } - else + /* For multicolumn index construct compound value */ + VacAttrStats *stats = thisdata->vacattrstats[0]; + exprvals[tcnt] = HeapTupleGetDatum(heap_form_tuple(stats->tupDesc, + values, + isnull)); + exprnulls[tcnt] = false; + tcnt++; + } + else + { + /* + * Save just the columns we care about. We copy the values + * into ind_context from the estate's per-tuple context. + */ + for (i = 0; i < attr_cnt; i++) { - exprvals[tcnt] = datumCopy(values[attnum - 1], - stats->attrtype->typbyval, - stats->attrtype->typlen); - exprnulls[tcnt] = false; + VacAttrStats *stats = thisdata->vacattrstats[i]; + int attnum = stats->attr->attnum; + + if (isnull[attnum - 1]) + { + exprvals[tcnt] = (Datum) 0; + exprnulls[tcnt] = true; + } + else + { + exprvals[tcnt] = datumCopy(values[attnum - 1], + stats->attrtype->typbyval, + stats->attrtype->typlen); + exprnulls[tcnt] = false; + } + tcnt++; } - tcnt++; } } } @@ -2578,6 +2611,7 @@ compute_scalar_stats(VacAttrStatsP stats, * histogram won't collapse to empty or a singleton.) */ num_hist = ndistinct - num_mcv; + if (num_hist > num_bins) num_hist = num_bins + 1; if (num_hist >= 2) diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index bc05c96b4ce..8e97d84b93c 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -378,21 +378,15 @@ ExplainOneQuery(Query *query, int cursorOptions, else { PlannedStmt *plan; - instr_time planstart, - planduration; BufferUsage bufusage_start, bufusage; if (es->buffers) bufusage_start = pgBufferUsage; - INSTR_TIME_SET_CURRENT(planstart); /* plan the query */ plan = pg_plan_query(query, queryString, cursorOptions, params); - INSTR_TIME_SET_CURRENT(planduration); - INSTR_TIME_SUBTRACT(planduration, planstart); - /* calc differences of buffer counters. */ if (es->buffers) { @@ -402,7 +396,7 @@ ExplainOneQuery(Query *query, int cursorOptions, /* run it (if needed) and produce output */ ExplainOnePlan(plan, into, es, queryString, params, queryEnv, - &planduration, (es->buffers ? &bufusage : NULL)); + &plan->planDuration, (es->buffers ? &bufusage : NULL)); } } diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c index ccffec91132..55006d6399e 100644 --- a/src/backend/commands/extension.c +++ b/src/backend/commands/extension.c @@ -1391,6 +1391,7 @@ static ObjectAddress CreateExtensionInternal(char *extensionName, char *schemaName, const char *versionName, + const char *oldVersionName, bool cascade, List *parents, bool is_create) @@ -1400,8 +1401,6 @@ CreateExtensionInternal(char *extensionName, Oid extowner = GetUserId(); ExtensionControlFile *pcontrol; ExtensionControlFile *control; - char *filename; - struct stat fst; List *updateVersions; List *requiredExtensions; List *requiredSchemas; @@ -1436,6 +1435,56 @@ CreateExtensionInternal(char *extensionName, * does what is needed, we try to find a sequence of update scripts that * will get us there. */ + if (oldVersionName) + { + /* + * "FROM old_version" was specified, indicating that we're trying to + * update from some unpackaged version of the extension. Locate a + * series of update scripts that will do it. + */ + check_valid_version_name(oldVersionName); + + if (strcmp(oldVersionName, versionName) == 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("FROM version must be different from installation target version \"%s\"", + versionName))); + + updateVersions = identify_update_path(pcontrol, + oldVersionName, + versionName); + + if (list_length(updateVersions) == 1) + { + /* + * Simple case where there's just one update script to run. We + * will not need any follow-on update steps. + */ + Assert(strcmp((char *) linitial(updateVersions), versionName) == 0); + updateVersions = NIL; + } + else + { + /* + * Multi-step sequence. We treat this as installing the version + * that is the target of the first script, followed by successive + * updates to the later versions. + */ + versionName = (char *) linitial(updateVersions); + updateVersions = list_delete_first(updateVersions); + } + } + else + { + /* + * No FROM, so we're installing from scratch. If there is an install + * script for the desired version, we only need to run that one. + */ + char *filename; + struct stat fst; + + oldVersionName = NULL; + filename = get_extension_script_filename(pcontrol, NULL, versionName); if (stat(filename, &fst) == 0) { @@ -1469,6 +1518,7 @@ CreateExtensionInternal(char *extensionName, /* Otherwise, install best starting point and then upgrade */ versionName = evi_start->name; } + } /* * Fetch control parameters for installation target version @@ -1608,7 +1658,7 @@ CreateExtensionInternal(char *extensionName, * Execute the installation script file */ execute_extension_script(extensionOid, control, - NULL, versionName, + oldVersionName, versionName, requiredSchemas, schemaName, schemaOid); @@ -1675,6 +1725,7 @@ get_required_extension(char *reqExtensionName, addr = CreateExtensionInternal(reqExtensionName, origSchemaName, NULL, + NULL, cascade, cascade_parents, is_create); @@ -1702,9 +1753,11 @@ CreateExtension(ParseState *pstate, CreateExtensionStmt *stmt) { DefElem *d_schema = NULL; DefElem *d_new_version = NULL; + DefElem *d_old_version = NULL; DefElem *d_cascade = NULL; char *schemaName = NULL; char *versionName = NULL; + char *oldVersionName = NULL; bool cascade = false; ListCell *lc; @@ -1768,6 +1821,16 @@ CreateExtension(ParseState *pstate, CreateExtensionStmt *stmt) d_new_version = defel; versionName = defGetString(d_new_version); } + else if (strcmp(defel->defname, "old_version") == 0) + { + if (d_old_version) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("conflicting or redundant options"), + parser_errposition(pstate, defel->location))); + d_old_version = defel; + oldVersionName = defGetString(d_old_version); + } else if (strcmp(defel->defname, "cascade") == 0) { if (d_cascade) @@ -1786,6 +1849,7 @@ CreateExtension(ParseState *pstate, CreateExtensionStmt *stmt) return CreateExtensionInternal(stmt->extname, schemaName, versionName, + oldVersionName, cascade, NIL, true); diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c index 8b64ba8aefe..93f1605ff17 100644 --- a/src/backend/executor/execExpr.c +++ b/src/backend/executor/execExpr.c @@ -251,13 +251,33 @@ ExecInitQual(List *qual, PlanState *parent) foreach(lc, qual) { Expr *node = (Expr *) lfirst(lc); + ExprEvalStep *lastStep; /* first evaluate expression */ ExecInitExprRec(node, state, &state->resvalue, &state->resnull); /* then emit EEOP_QUAL to detect if it's false (or null) */ scratch.d.qualexpr.jumpdone = -1; + + lastStep = &state->steps[state->steps_len-1]; + if (list_length(qual) == 1 && + (lastStep->opcode == EEOP_BOOL_OR_STEP_LAST || + lastStep->opcode == EEOP_BOOL_AND_STEP_LAST)) + scratch.d.qualexpr.guaranteed_empty = + lastStep->d.boolexpr.guaranteed_empty; + else if (list_length(qual) == 1 && + lastStep->opcode == EEOP_ALTERNATIVE_SUBPLAN) + { + scratch.d.qualexpr.guaranteed_empty = + lastStep->d.alternative_subplan.guaranteed_empty = + palloc(sizeof(bool)); + *scratch.d.qualexpr.guaranteed_empty = false; + } + else + scratch.d.qualexpr.guaranteed_empty = NULL; + ExprEvalPushStep(state, &scratch); + adjust_jumps = lappend_int(adjust_jumps, state->steps_len - 1); } @@ -1040,8 +1060,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 @@ -1060,10 +1087,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) { @@ -1165,6 +1199,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; @@ -3580,6 +3615,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 21cbf513f41..2be41d94ee8 100644 --- a/src/backend/executor/execExprInterp.c +++ b/src/backend/executor/execExprInterp.c @@ -760,6 +760,9 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) EEO_JUMP(op->d.boolexpr.jumpdone); } + /* reset */ + *op->d.boolexpr.guaranteed_empty = false; + EEO_NEXT(); } @@ -768,6 +771,8 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) if (*op->resnull) { /* result is already set to NULL, need not change it */ + /* reset */ + *op->d.boolexpr.guaranteed_empty = false; } else if (!DatumGetBool(*op->resvalue)) { @@ -783,10 +788,15 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) { *op->resvalue = (Datum) 0; *op->resnull = true; + /* reset */ + *op->d.boolexpr.guaranteed_empty = false; } else { /* result is already set to TRUE, need not change it */ + /* reset */ + *op->d.boolexpr.guaranteed_empty = false; + } EEO_NEXT(); @@ -805,6 +815,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) EEO_CASE(EEOP_BOOL_OR_STEP_FIRST) { *op->d.boolexpr.anynull = false; + *op->d.boolexpr.count_guaranteed_empty = 0; /* * EEOP_BOOL_OR_STEP_FIRST resets anynull, otherwise it's the same @@ -816,6 +827,10 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) EEO_CASE(EEOP_BOOL_OR_STEP) { + *op->d.boolexpr.count_guaranteed_empty += + (int) (*op->d.boolexpr.guaranteed_empty); + *op->d.boolexpr.guaranteed_empty = false; + if (*op->resnull) { *op->d.boolexpr.anynull = true; @@ -832,6 +847,10 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) EEO_CASE(EEOP_BOOL_OR_STEP_LAST) { + *op->d.boolexpr.count_guaranteed_empty += + (int) (*op->d.boolexpr.guaranteed_empty); + *op->d.boolexpr.guaranteed_empty = false; + if (*op->resnull) { /* result is already set to NULL, need not change it */ @@ -853,6 +872,10 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) } else { + if (*op->d.boolexpr.count_guaranteed_empty == op->d.boolexpr.nargs) + *op->d.boolexpr.guaranteed_empty = true; + else + *op->d.boolexpr.guaranteed_empty = false; /* result is already set to FALSE, need not change it */ } @@ -883,6 +906,9 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) /* ... bail out early, returning FALSE */ *op->resnull = false; *op->resvalue = BoolGetDatum(false); + if (op->d.qualexpr.guaranteed_empty && + op - state->steps == state->steps_len - 2 /* + EEOP_DONE */) + state->guaranteed_empty = *op->d.qualexpr.guaranteed_empty; EEO_JUMP(op->d.qualexpr.jumpdone); } @@ -3922,7 +3948,16 @@ ExecEvalAlternativeSubPlan(ExprState *state, ExprEvalStep *op, ExprContext *econ /* could potentially be nested, so make sure there's enough stack */ check_stack_depth(); - *op->resvalue = ExecAlternativeSubPlan(asstate, econtext, op->resnull); + if (asstate->guaranteed_empty == false) + *op->resvalue = ExecAlternativeSubPlan(asstate, econtext, op->resnull); + else + { + *op->resvalue = false; + *op->resnull = false; + } + + if (op->d.alternative_subplan.guaranteed_empty) + *op->d.alternative_subplan.guaranteed_empty = asstate->guaranteed_empty; } /* diff --git a/src/backend/executor/execScan.c b/src/backend/executor/execScan.c index 642805d90cd..2d2e571189d 100644 --- a/src/backend/executor/execScan.c +++ b/src/backend/executor/execScan.c @@ -187,6 +187,7 @@ ExecScan(ScanState *node, * storage allocated in the previous tuple cycle. */ ResetExprContext(econtext); + node->ps.guaranteed_empty = false; /* * get a tuple from the access method. Loop until we obtain a tuple that @@ -245,7 +246,14 @@ ExecScan(ScanState *node, return slot; } } - else + else if (qual && qual->guaranteed_empty) + { + /* Qual guarantees the absence of results */ + node->ps.guaranteed_empty = true; + ExecClearTuple(slot); + + return slot; + } else InstrCountFiltered1(node, 1); /* diff --git a/src/backend/executor/nodeMaterial.c b/src/backend/executor/nodeMaterial.c index dd077f43230..cae566402dd 100644 --- a/src/backend/executor/nodeMaterial.c +++ b/src/backend/executor/nodeMaterial.c @@ -135,6 +135,8 @@ ExecMaterial(PlanState *pstate) if (TupIsNull(outerslot)) { node->eof_underlying = true; + if (tuplestore_tuple_count(tuplestorestate) == 0) + node->ss.ps.guaranteed_empty = true; return NULL; } @@ -363,6 +365,9 @@ ExecReScanMaterial(MaterialState *node) */ if (outerPlan->chgParam == NULL) ExecReScan(outerPlan); + else + node->ss.ps.guaranteed_empty = false; + node->eof_underlying = false; } } diff --git a/src/backend/executor/nodeNestloop.c b/src/backend/executor/nodeNestloop.c index b07c2996d4c..be69381238d 100644 --- a/src/backend/executor/nodeNestloop.c +++ b/src/backend/executor/nodeNestloop.c @@ -164,6 +164,11 @@ ExecNestLoop(PlanState *pstate) { ENL1_printf("no inner tuple, need new outer tuple"); + if (innerPlan->guaranteed_empty && + (node->js.jointype == JOIN_INNER || + node->js.jointype == JOIN_SEMI)) + return NULL; + node->nl_NeedNewOuter = true; if (!node->nl_MatchedOuter && diff --git a/src/backend/executor/nodeSubplan.c b/src/backend/executor/nodeSubplan.c index 88604884cd8..db96c402e57 100644 --- a/src/backend/executor/nodeSubplan.c +++ b/src/backend/executor/nodeSubplan.c @@ -105,6 +105,9 @@ ExecHashSubPlan(SubPlanState *node, SubPlan *subplan = node->subplan; PlanState *planstate = node->planstate; TupleTableSlot *slot; + bool hasParam = (planstate->plan->extParam != NULL || + subplan->setParam != NIL || + planstate->chgParam != NULL); /* Shouldn't have any direct correlation Vars */ if (subplan->parParam != NIL || node->args != NIL) @@ -122,8 +125,11 @@ ExecHashSubPlan(SubPlanState *node, * lefthand side. */ *isNull = false; - if (!node->havehashrows && !node->havenullrows) + if (!node->havehashrows && !node->havenullrows) { + if (hasParam == false) + node->planstate->guaranteed_empty = true; return BoolGetDatum(false); + } /* * Evaluate lefthand expressions and form a projection tuple. First we @@ -1379,7 +1385,14 @@ ExecInitAlternativeSubPlan(AlternativeSubPlan *asplan, PlanState *parent) cost1 = subplan1->startup_cost + num_calls * subplan1->per_call_cost; cost2 = subplan2->startup_cost + num_calls * subplan2->per_call_cost; - if (cost1 < cost2) + /* + * Prefer hashed subplan if difference isn't very big. Hashed subplan could + * set guaranteed_empty flags so multiple execution will be prevented + */ + if (subplan1->useHashTable != subplan2->useHashTable && + Abs(cost1 - cost2) < 0.1 * Max(cost1, cost2)) + asstate->active = (subplan1->useHashTable) ? 0 : 1; + else if (cost1 < cost2) asstate->active = 0; else asstate->active = 1; @@ -1403,6 +1416,12 @@ ExecAlternativeSubPlan(AlternativeSubPlanState *node, /* Just pass control to the active subplan */ SubPlanState *activesp = list_nth_node(SubPlanState, node->subplans, node->active); + Datum res = ExecSubPlan(activesp, econtext, isNull); + + if (activesp->subplan->useHashTable && activesp->planstate->guaranteed_empty) + node->guaranteed_empty = true; + else + node->guaranteed_empty = false; - return ExecSubPlan(activesp, econtext, isNull); + return res; } diff --git a/src/backend/libpq/pqcomm.c b/src/backend/libpq/pqcomm.c index 93f2e0b81d3..b2f277dfe35 100644 --- a/src/backend/libpq/pqcomm.c +++ b/src/backend/libpq/pqcomm.c @@ -81,6 +81,9 @@ #ifdef HAVE_NETINET_TCP_H #include <netinet/tcp.h> #endif +#ifdef HAVE_POLL_H +#include <poll.h> +#endif #include <utime.h> #ifdef _MSC_VER /* mstcpip.h is missing on mingw */ #include <mstcpip.h> @@ -1994,3 +1997,30 @@ pq_settcpusertimeout(int timeout, Port *port) return STATUS_OK; } + +void +pq_check_client_connection(void) +{ + CheckClientConnectionPending = false; +#ifndef WIN32 + if (IsUnderPostmaster && + MyProcPort != NULL && !PqCommReadingMsg && !PqCommBusy) + { + int r; + struct pollfd fd; + + fd.fd = MyProcPort->sock; + fd.events = 0; + fd.revents = 0; + + r = poll(&fd, 1, 0); + + if ((r < 0 && errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) + || (r>=0 && fd.revents & (POLLHUP | POLLERR)) != 0) + { + ClientConnectionLost = true; + InterruptPending = true; + } + } +#endif +} diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c index 585b0046f4f..c860aa98525 100644 --- a/src/backend/nodes/nodeFuncs.c +++ b/src/backend/nodes/nodeFuncs.c @@ -2572,21 +2572,40 @@ range_table_entry_walker(RangeTblEntry *rte, Node * expression_tree_mutator(Node *node, Node *(*mutator) (), - void *context) + void *context, + int flags) { /* * The mutator has already decided not to modify the current node, but we * must call the mutator for any sub-nodes. */ -#define FLATCOPY(newnode, node, nodetype) \ - ( (newnode) = (nodetype *) palloc(sizeof(nodetype)), \ - memcpy((newnode), (node), sizeof(nodetype)) ) - -#define CHECKFLATCOPY(newnode, node, nodetype) \ - ( AssertMacro(IsA((node), nodetype)), \ - (newnode) = (nodetype *) palloc(sizeof(nodetype)), \ - memcpy((newnode), (node), sizeof(nodetype)) ) +#define FLATCOPY(newnode, node, nodetype, flags) \ + do { \ + if ((flags) & QTW_DONT_COPY_DEFAULT) \ + { \ + (newnode) = (node); \ + } \ + else \ + { \ + (newnode) = (nodetype *) palloc(sizeof(nodetype)); \ + memcpy((newnode), (node), sizeof(nodetype)); \ + } \ + } while(0) + +#define CHECKFLATCOPY(newnode, node, nodetype, flags) \ + do { \ + AssertMacro(IsA((node), nodetype)); \ + if ((flags) & QTW_DONT_COPY_DEFAULT) \ + { \ + (newnode) = (node); \ + } \ + else \ + { \ + (newnode) = (nodetype *) palloc(sizeof(nodetype)); \ + memcpy((newnode), (node), sizeof(nodetype)); \ + } \ + } while(0) #define MUTATE(newfield, oldfield, fieldtype) \ ( (newfield) = (fieldtype) mutator((Node *) (oldfield), context) ) @@ -2609,7 +2628,7 @@ expression_tree_mutator(Node *node, Var *var = (Var *) node; Var *newnode; - FLATCOPY(newnode, var, Var); + FLATCOPY(newnode, var, Var, flags); return (Node *) newnode; } break; @@ -2618,7 +2637,7 @@ expression_tree_mutator(Node *node, Const *oldnode = (Const *) node; Const *newnode; - FLATCOPY(newnode, oldnode, Const); + FLATCOPY(newnode, oldnode, Const, flags); /* XXX we don't bother with datumCopy; should we? */ return (Node *) newnode; } @@ -2638,7 +2657,7 @@ expression_tree_mutator(Node *node, WithCheckOption *wco = (WithCheckOption *) node; WithCheckOption *newnode; - FLATCOPY(newnode, wco, WithCheckOption); + FLATCOPY(newnode, wco, WithCheckOption, flags); MUTATE(newnode->qual, wco->qual, Node *); return (Node *) newnode; } @@ -2647,7 +2666,7 @@ expression_tree_mutator(Node *node, Aggref *aggref = (Aggref *) node; Aggref *newnode; - FLATCOPY(newnode, aggref, Aggref); + FLATCOPY(newnode, aggref, Aggref, flags); /* assume mutation doesn't change types of arguments */ newnode->aggargtypes = list_copy(aggref->aggargtypes); MUTATE(newnode->aggdirectargs, aggref->aggdirectargs, List *); @@ -2663,7 +2682,7 @@ expression_tree_mutator(Node *node, GroupingFunc *grouping = (GroupingFunc *) node; GroupingFunc *newnode; - FLATCOPY(newnode, grouping, GroupingFunc); + FLATCOPY(newnode, grouping, GroupingFunc, flags); MUTATE(newnode->args, grouping->args, List *); /* @@ -2686,7 +2705,7 @@ expression_tree_mutator(Node *node, WindowFunc *wfunc = (WindowFunc *) node; WindowFunc *newnode; - FLATCOPY(newnode, wfunc, WindowFunc); + FLATCOPY(newnode, wfunc, WindowFunc, flags); MUTATE(newnode->args, wfunc->args, List *); MUTATE(newnode->aggfilter, wfunc->aggfilter, Expr *); return (Node *) newnode; @@ -2697,7 +2716,7 @@ expression_tree_mutator(Node *node, SubscriptingRef *sbsref = (SubscriptingRef *) node; SubscriptingRef *newnode; - FLATCOPY(newnode, sbsref, SubscriptingRef); + FLATCOPY(newnode, sbsref, SubscriptingRef, flags); MUTATE(newnode->refupperindexpr, sbsref->refupperindexpr, List *); MUTATE(newnode->reflowerindexpr, sbsref->reflowerindexpr, @@ -2715,7 +2734,7 @@ expression_tree_mutator(Node *node, FuncExpr *expr = (FuncExpr *) node; FuncExpr *newnode; - FLATCOPY(newnode, expr, FuncExpr); + FLATCOPY(newnode, expr, FuncExpr, flags); MUTATE(newnode->args, expr->args, List *); return (Node *) newnode; } @@ -2725,7 +2744,7 @@ expression_tree_mutator(Node *node, NamedArgExpr *nexpr = (NamedArgExpr *) node; NamedArgExpr *newnode; - FLATCOPY(newnode, nexpr, NamedArgExpr); + FLATCOPY(newnode, nexpr, NamedArgExpr, flags); MUTATE(newnode->arg, nexpr->arg, Expr *); return (Node *) newnode; } @@ -2735,7 +2754,7 @@ expression_tree_mutator(Node *node, OpExpr *expr = (OpExpr *) node; OpExpr *newnode; - FLATCOPY(newnode, expr, OpExpr); + FLATCOPY(newnode, expr, OpExpr, flags & ~QTW_DONT_COPY_DEFAULT); MUTATE(newnode->args, expr->args, List *); return (Node *) newnode; } @@ -2745,7 +2764,7 @@ expression_tree_mutator(Node *node, DistinctExpr *expr = (DistinctExpr *) node; DistinctExpr *newnode; - FLATCOPY(newnode, expr, DistinctExpr); + FLATCOPY(newnode, expr, DistinctExpr, flags); MUTATE(newnode->args, expr->args, List *); return (Node *) newnode; } @@ -2755,7 +2774,7 @@ expression_tree_mutator(Node *node, NullIfExpr *expr = (NullIfExpr *) node; NullIfExpr *newnode; - FLATCOPY(newnode, expr, NullIfExpr); + FLATCOPY(newnode, expr, NullIfExpr, flags); MUTATE(newnode->args, expr->args, List *); return (Node *) newnode; } @@ -2765,7 +2784,7 @@ expression_tree_mutator(Node *node, ScalarArrayOpExpr *expr = (ScalarArrayOpExpr *) node; ScalarArrayOpExpr *newnode; - FLATCOPY(newnode, expr, ScalarArrayOpExpr); + FLATCOPY(newnode, expr, ScalarArrayOpExpr, flags); MUTATE(newnode->args, expr->args, List *); return (Node *) newnode; } @@ -2775,7 +2794,7 @@ expression_tree_mutator(Node *node, BoolExpr *expr = (BoolExpr *) node; BoolExpr *newnode; - FLATCOPY(newnode, expr, BoolExpr); + FLATCOPY(newnode, expr, BoolExpr, flags); MUTATE(newnode->args, expr->args, List *); return (Node *) newnode; } @@ -2785,7 +2804,7 @@ expression_tree_mutator(Node *node, SubLink *sublink = (SubLink *) node; SubLink *newnode; - FLATCOPY(newnode, sublink, SubLink); + FLATCOPY(newnode, sublink, SubLink, flags); MUTATE(newnode->testexpr, sublink->testexpr, Node *); /* @@ -2801,7 +2820,7 @@ expression_tree_mutator(Node *node, SubPlan *subplan = (SubPlan *) node; SubPlan *newnode; - FLATCOPY(newnode, subplan, SubPlan); + FLATCOPY(newnode, subplan, SubPlan, flags); /* transform testexpr */ MUTATE(newnode->testexpr, subplan->testexpr, Node *); /* transform args list (params to be passed to subplan) */ @@ -2815,7 +2834,7 @@ expression_tree_mutator(Node *node, AlternativeSubPlan *asplan = (AlternativeSubPlan *) node; AlternativeSubPlan *newnode; - FLATCOPY(newnode, asplan, AlternativeSubPlan); + FLATCOPY(newnode, asplan, AlternativeSubPlan, flags); MUTATE(newnode->subplans, asplan->subplans, List *); return (Node *) newnode; } @@ -2825,7 +2844,7 @@ expression_tree_mutator(Node *node, FieldSelect *fselect = (FieldSelect *) node; FieldSelect *newnode; - FLATCOPY(newnode, fselect, FieldSelect); + FLATCOPY(newnode, fselect, FieldSelect, flags); MUTATE(newnode->arg, fselect->arg, Expr *); return (Node *) newnode; } @@ -2835,7 +2854,7 @@ expression_tree_mutator(Node *node, FieldStore *fstore = (FieldStore *) node; FieldStore *newnode; - FLATCOPY(newnode, fstore, FieldStore); + FLATCOPY(newnode, fstore, FieldStore, flags); MUTATE(newnode->arg, fstore->arg, Expr *); MUTATE(newnode->newvals, fstore->newvals, List *); newnode->fieldnums = list_copy(fstore->fieldnums); @@ -2847,7 +2866,7 @@ expression_tree_mutator(Node *node, RelabelType *relabel = (RelabelType *) node; RelabelType *newnode; - FLATCOPY(newnode, relabel, RelabelType); + FLATCOPY(newnode, relabel, RelabelType, flags); MUTATE(newnode->arg, relabel->arg, Expr *); return (Node *) newnode; } @@ -2857,7 +2876,7 @@ expression_tree_mutator(Node *node, CoerceViaIO *iocoerce = (CoerceViaIO *) node; CoerceViaIO *newnode; - FLATCOPY(newnode, iocoerce, CoerceViaIO); + FLATCOPY(newnode, iocoerce, CoerceViaIO, flags); MUTATE(newnode->arg, iocoerce->arg, Expr *); return (Node *) newnode; } @@ -2867,7 +2886,7 @@ expression_tree_mutator(Node *node, ArrayCoerceExpr *acoerce = (ArrayCoerceExpr *) node; ArrayCoerceExpr *newnode; - FLATCOPY(newnode, acoerce, ArrayCoerceExpr); + FLATCOPY(newnode, acoerce, ArrayCoerceExpr, flags); MUTATE(newnode->arg, acoerce->arg, Expr *); MUTATE(newnode->elemexpr, acoerce->elemexpr, Expr *); return (Node *) newnode; @@ -2878,7 +2897,7 @@ expression_tree_mutator(Node *node, ConvertRowtypeExpr *convexpr = (ConvertRowtypeExpr *) node; ConvertRowtypeExpr *newnode; - FLATCOPY(newnode, convexpr, ConvertRowtypeExpr); + FLATCOPY(newnode, convexpr, ConvertRowtypeExpr, flags); MUTATE(newnode->arg, convexpr->arg, Expr *); return (Node *) newnode; } @@ -2888,7 +2907,7 @@ expression_tree_mutator(Node *node, CollateExpr *collate = (CollateExpr *) node; CollateExpr *newnode; - FLATCOPY(newnode, collate, CollateExpr); + FLATCOPY(newnode, collate, CollateExpr, flags); MUTATE(newnode->arg, collate->arg, Expr *); return (Node *) newnode; } @@ -2898,7 +2917,7 @@ expression_tree_mutator(Node *node, CaseExpr *caseexpr = (CaseExpr *) node; CaseExpr *newnode; - FLATCOPY(newnode, caseexpr, CaseExpr); + FLATCOPY(newnode, caseexpr, CaseExpr, flags); MUTATE(newnode->arg, caseexpr->arg, Expr *); MUTATE(newnode->args, caseexpr->args, List *); MUTATE(newnode->defresult, caseexpr->defresult, Expr *); @@ -2910,7 +2929,7 @@ expression_tree_mutator(Node *node, CaseWhen *casewhen = (CaseWhen *) node; CaseWhen *newnode; - FLATCOPY(newnode, casewhen, CaseWhen); + FLATCOPY(newnode, casewhen, CaseWhen, flags); MUTATE(newnode->expr, casewhen->expr, Expr *); MUTATE(newnode->result, casewhen->result, Expr *); return (Node *) newnode; @@ -2921,7 +2940,7 @@ expression_tree_mutator(Node *node, ArrayExpr *arrayexpr = (ArrayExpr *) node; ArrayExpr *newnode; - FLATCOPY(newnode, arrayexpr, ArrayExpr); + FLATCOPY(newnode, arrayexpr, ArrayExpr, flags); MUTATE(newnode->elements, arrayexpr->elements, List *); return (Node *) newnode; } @@ -2931,7 +2950,7 @@ expression_tree_mutator(Node *node, RowExpr *rowexpr = (RowExpr *) node; RowExpr *newnode; - FLATCOPY(newnode, rowexpr, RowExpr); + FLATCOPY(newnode, rowexpr, RowExpr, flags); MUTATE(newnode->args, rowexpr->args, List *); /* Assume colnames needn't be duplicated */ return (Node *) newnode; @@ -2942,7 +2961,7 @@ expression_tree_mutator(Node *node, RowCompareExpr *rcexpr = (RowCompareExpr *) node; RowCompareExpr *newnode; - FLATCOPY(newnode, rcexpr, RowCompareExpr); + FLATCOPY(newnode, rcexpr, RowCompareExpr, flags); MUTATE(newnode->largs, rcexpr->largs, List *); MUTATE(newnode->rargs, rcexpr->rargs, List *); return (Node *) newnode; @@ -2953,7 +2972,7 @@ expression_tree_mutator(Node *node, CoalesceExpr *coalesceexpr = (CoalesceExpr *) node; CoalesceExpr *newnode; - FLATCOPY(newnode, coalesceexpr, CoalesceExpr); + FLATCOPY(newnode, coalesceexpr, CoalesceExpr, flags); MUTATE(newnode->args, coalesceexpr->args, List *); return (Node *) newnode; } @@ -2963,7 +2982,7 @@ expression_tree_mutator(Node *node, MinMaxExpr *minmaxexpr = (MinMaxExpr *) node; MinMaxExpr *newnode; - FLATCOPY(newnode, minmaxexpr, MinMaxExpr); + FLATCOPY(newnode, minmaxexpr, MinMaxExpr, flags); MUTATE(newnode->args, minmaxexpr->args, List *); return (Node *) newnode; } @@ -2973,7 +2992,7 @@ expression_tree_mutator(Node *node, XmlExpr *xexpr = (XmlExpr *) node; XmlExpr *newnode; - FLATCOPY(newnode, xexpr, XmlExpr); + FLATCOPY(newnode, xexpr, XmlExpr, flags); MUTATE(newnode->named_args, xexpr->named_args, List *); /* assume mutator does not care about arg_names */ MUTATE(newnode->args, xexpr->args, List *); @@ -2985,7 +3004,7 @@ expression_tree_mutator(Node *node, NullTest *ntest = (NullTest *) node; NullTest *newnode; - FLATCOPY(newnode, ntest, NullTest); + FLATCOPY(newnode, ntest, NullTest, flags); MUTATE(newnode->arg, ntest->arg, Expr *); return (Node *) newnode; } @@ -2995,7 +3014,7 @@ expression_tree_mutator(Node *node, BooleanTest *btest = (BooleanTest *) node; BooleanTest *newnode; - FLATCOPY(newnode, btest, BooleanTest); + FLATCOPY(newnode, btest, BooleanTest, flags); MUTATE(newnode->arg, btest->arg, Expr *); return (Node *) newnode; } @@ -3005,7 +3024,7 @@ expression_tree_mutator(Node *node, CoerceToDomain *ctest = (CoerceToDomain *) node; CoerceToDomain *newnode; - FLATCOPY(newnode, ctest, CoerceToDomain); + FLATCOPY(newnode, ctest, CoerceToDomain, flags); MUTATE(newnode->arg, ctest->arg, Expr *); return (Node *) newnode; } @@ -3015,7 +3034,7 @@ expression_tree_mutator(Node *node, TargetEntry *targetentry = (TargetEntry *) node; TargetEntry *newnode; - FLATCOPY(newnode, targetentry, TargetEntry); + FLATCOPY(newnode, targetentry, TargetEntry, flags & ~QTW_DONT_COPY_DEFAULT); MUTATE(newnode->expr, targetentry->expr, Expr *); return (Node *) newnode; } @@ -3028,7 +3047,7 @@ expression_tree_mutator(Node *node, WindowClause *wc = (WindowClause *) node; WindowClause *newnode; - FLATCOPY(newnode, wc, WindowClause); + FLATCOPY(newnode, wc, WindowClause, flags); MUTATE(newnode->partitionClause, wc->partitionClause, List *); MUTATE(newnode->orderClause, wc->orderClause, List *); MUTATE(newnode->startOffset, wc->startOffset, Node *); @@ -3041,7 +3060,7 @@ expression_tree_mutator(Node *node, CommonTableExpr *cte = (CommonTableExpr *) node; CommonTableExpr *newnode; - FLATCOPY(newnode, cte, CommonTableExpr); + FLATCOPY(newnode, cte, CommonTableExpr, flags); /* * Also invoke the mutator on the CTE's Query node, so it can @@ -3076,7 +3095,7 @@ expression_tree_mutator(Node *node, FromExpr *from = (FromExpr *) node; FromExpr *newnode; - FLATCOPY(newnode, from, FromExpr); + FLATCOPY(newnode, from, FromExpr, flags); MUTATE(newnode->fromlist, from->fromlist, List *); MUTATE(newnode->quals, from->quals, Node *); return (Node *) newnode; @@ -3087,7 +3106,7 @@ expression_tree_mutator(Node *node, OnConflictExpr *oc = (OnConflictExpr *) node; OnConflictExpr *newnode; - FLATCOPY(newnode, oc, OnConflictExpr); + FLATCOPY(newnode, oc, OnConflictExpr, flags); MUTATE(newnode->arbiterElems, oc->arbiterElems, List *); MUTATE(newnode->arbiterWhere, oc->arbiterWhere, Node *); MUTATE(newnode->onConflictSet, oc->onConflictSet, List *); @@ -3102,7 +3121,7 @@ expression_tree_mutator(Node *node, PartitionPruneStepOp *opstep = (PartitionPruneStepOp *) node; PartitionPruneStepOp *newnode; - FLATCOPY(newnode, opstep, PartitionPruneStepOp); + FLATCOPY(newnode, opstep, PartitionPruneStepOp, flags); MUTATE(newnode->exprs, opstep->exprs, List *); return (Node *) newnode; @@ -3116,7 +3135,7 @@ expression_tree_mutator(Node *node, JoinExpr *join = (JoinExpr *) node; JoinExpr *newnode; - FLATCOPY(newnode, join, JoinExpr); + FLATCOPY(newnode, join, JoinExpr, flags); MUTATE(newnode->larg, join->larg, Node *); MUTATE(newnode->rarg, join->rarg, Node *); MUTATE(newnode->quals, join->quals, Node *); @@ -3129,7 +3148,7 @@ expression_tree_mutator(Node *node, SetOperationStmt *setop = (SetOperationStmt *) node; SetOperationStmt *newnode; - FLATCOPY(newnode, setop, SetOperationStmt); + FLATCOPY(newnode, setop, SetOperationStmt, flags); MUTATE(newnode->larg, setop->larg, Node *); MUTATE(newnode->rarg, setop->rarg, Node *); /* We do not mutate groupClauses by default */ @@ -3141,7 +3160,7 @@ expression_tree_mutator(Node *node, IndexClause *iclause = (IndexClause *) node; IndexClause *newnode; - FLATCOPY(newnode, iclause, IndexClause); + FLATCOPY(newnode, iclause, IndexClause, flags); MUTATE(newnode->rinfo, iclause->rinfo, RestrictInfo *); MUTATE(newnode->indexquals, iclause->indexquals, List *); return (Node *) newnode; @@ -3152,7 +3171,7 @@ expression_tree_mutator(Node *node, PlaceHolderVar *phv = (PlaceHolderVar *) node; PlaceHolderVar *newnode; - FLATCOPY(newnode, phv, PlaceHolderVar); + FLATCOPY(newnode, phv, PlaceHolderVar, flags); MUTATE(newnode->phexpr, phv->phexpr, Expr *); /* Assume we need not copy the relids bitmapset */ return (Node *) newnode; @@ -3163,7 +3182,7 @@ expression_tree_mutator(Node *node, InferenceElem *inferenceelemdexpr = (InferenceElem *) node; InferenceElem *newnode; - FLATCOPY(newnode, inferenceelemdexpr, InferenceElem); + FLATCOPY(newnode, inferenceelemdexpr, InferenceElem, flags); MUTATE(newnode->expr, newnode->expr, Node *); return (Node *) newnode; } @@ -3173,7 +3192,7 @@ expression_tree_mutator(Node *node, AppendRelInfo *appinfo = (AppendRelInfo *) node; AppendRelInfo *newnode; - FLATCOPY(newnode, appinfo, AppendRelInfo); + FLATCOPY(newnode, appinfo, AppendRelInfo, flags); MUTATE(newnode->translated_vars, appinfo->translated_vars, List *); /* Assume nothing need be done with parent_colnos[] */ return (Node *) newnode; @@ -3184,7 +3203,7 @@ expression_tree_mutator(Node *node, PlaceHolderInfo *phinfo = (PlaceHolderInfo *) node; PlaceHolderInfo *newnode; - FLATCOPY(newnode, phinfo, PlaceHolderInfo); + FLATCOPY(newnode, phinfo, PlaceHolderInfo, flags); MUTATE(newnode->ph_var, phinfo->ph_var, PlaceHolderVar *); /* Assume we need not copy the relids bitmapsets */ return (Node *) newnode; @@ -3195,7 +3214,7 @@ expression_tree_mutator(Node *node, RangeTblFunction *rtfunc = (RangeTblFunction *) node; RangeTblFunction *newnode; - FLATCOPY(newnode, rtfunc, RangeTblFunction); + FLATCOPY(newnode, rtfunc, RangeTblFunction, flags); MUTATE(newnode->funcexpr, rtfunc->funcexpr, Node *); /* Assume we need not copy the coldef info lists */ return (Node *) newnode; @@ -3206,7 +3225,7 @@ expression_tree_mutator(Node *node, TableSampleClause *tsc = (TableSampleClause *) node; TableSampleClause *newnode; - FLATCOPY(newnode, tsc, TableSampleClause); + FLATCOPY(newnode, tsc, TableSampleClause, flags); MUTATE(newnode->args, tsc->args, List *); MUTATE(newnode->repeatable, tsc->repeatable, Expr *); return (Node *) newnode; @@ -3217,7 +3236,7 @@ expression_tree_mutator(Node *node, TableFunc *tf = (TableFunc *) node; TableFunc *newnode; - FLATCOPY(newnode, tf, TableFunc); + FLATCOPY(newnode, tf, TableFunc, flags); MUTATE(newnode->ns_uris, tf->ns_uris, List *); MUTATE(newnode->docexpr, tf->docexpr, Node *); MUTATE(newnode->rowexpr, tf->rowexpr, Node *); @@ -3267,7 +3286,7 @@ query_tree_mutator(Query *query, { Query *newquery; - FLATCOPY(newquery, query, Query); + FLATCOPY(newquery, query, Query, flags); query = newquery; } @@ -3309,7 +3328,7 @@ query_tree_mutator(Query *query, WindowClause *wc = lfirst_node(WindowClause, temp); WindowClause *newnode; - FLATCOPY(newnode, wc, WindowClause); + FLATCOPY(newnode, wc, WindowClause, flags); MUTATE(newnode->startOffset, wc->startOffset, Node *); MUTATE(newnode->endOffset, wc->endOffset, Node *); @@ -3358,7 +3377,7 @@ range_table_mutator(List *rtable, RangeTblEntry *rte = (RangeTblEntry *) lfirst(rt); RangeTblEntry *newrte; - FLATCOPY(newrte, rte, RangeTblEntry); + FLATCOPY(newrte, rte, RangeTblEntry, flags); switch (rte->rtekind) { case RTE_RELATION: @@ -3369,7 +3388,7 @@ range_table_mutator(List *rtable, case RTE_SUBQUERY: if (!(flags & QTW_IGNORE_RT_SUBQUERIES)) { - CHECKFLATCOPY(newrte->subquery, rte->subquery, Query); + CHECKFLATCOPY(newrte->subquery, rte->subquery, Query, flags); MUTATE(newrte->subquery, newrte->subquery, Query *); } else diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 21ececf0c2f..9fb3490d9fa 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -1875,6 +1875,7 @@ _outAppendPath(StringInfo str, const AppendPath *node) WRITE_NODE_FIELD(partitioned_rels); WRITE_NODE_FIELD(subpaths); WRITE_INT_FIELD(first_partial_path); + WRITE_BOOL_FIELD(pull_tlist); WRITE_FLOAT_FIELD(limit_tuples, "%.0f"); } diff --git a/src/backend/optimizer/path/Makefile b/src/backend/optimizer/path/Makefile index 1e199ff66f7..06dd07f3270 100644 --- a/src/backend/optimizer/path/Makefile +++ b/src/backend/optimizer/path/Makefile @@ -14,6 +14,7 @@ include $(top_builddir)/src/Makefile.global OBJS = \ allpaths.o \ + appendorpath.o \ clausesel.o \ costsize.o \ equivclass.o \ diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 00e37620ff3..6f7c9fa0ed0 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -784,6 +784,9 @@ set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) /* Consider index scans */ create_index_paths(root, rel); + /* Consider index scans with rewrited quals */ + keybased_rewrite_index_paths(root, rel); + /* Consider TID scans */ create_tidscan_paths(root, rel); } @@ -1525,7 +1528,7 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel, if (subpaths_valid) add_path(rel, (Path *) create_append_path(root, rel, subpaths, NIL, NIL, NULL, 0, false, - partitioned_rels, -1)); + partitioned_rels, -1, false)); /* * Consider an append of unordered, unparameterized partial paths. Make @@ -1568,7 +1571,8 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel, appendpath = create_append_path(root, rel, NIL, partial_subpaths, NIL, NULL, parallel_workers, enable_parallel_append, - partitioned_rels, -1); + partitioned_rels, -1, + false); /* * Make sure any subsequent partial paths use the same row count @@ -1617,7 +1621,7 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel, appendpath = create_append_path(root, rel, pa_nonpartial_subpaths, pa_partial_subpaths, NIL, NULL, parallel_workers, true, - partitioned_rels, partial_rows); + partitioned_rels, partial_rows, false); add_partial_path(rel, (Path *) appendpath); } @@ -1679,7 +1683,7 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel, add_path(rel, (Path *) create_append_path(root, rel, subpaths, NIL, NIL, required_outer, 0, false, - partitioned_rels, -1)); + partitioned_rels, -1, false)); } /* @@ -1709,7 +1713,8 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel, appendpath = create_append_path(root, rel, NIL, list_make1(path), NIL, NULL, path->parallel_workers, true, - partitioned_rels, partial_rows); + partitioned_rels, partial_rows, + false); add_partial_path(rel, (Path *) appendpath); } } @@ -1913,7 +1918,7 @@ generate_orderedappend_paths(PlannerInfo *root, RelOptInfo *rel, 0, false, partitioned_rels, - -1)); + -1, false)); if (startup_neq_total) add_path(rel, (Path *) create_append_path(root, rel, @@ -1924,7 +1929,7 @@ generate_orderedappend_paths(PlannerInfo *root, RelOptInfo *rel, 0, false, partitioned_rels, - -1)); + -1, false)); } else { @@ -2135,7 +2140,7 @@ set_dummy_rel_pathlist(RelOptInfo *rel) /* Set up the dummy path */ add_path(rel, (Path *) create_append_path(NULL, rel, NIL, NIL, NIL, rel->lateral_relids, - 0, false, NIL, -1)); + 0, false, NIL, -1, false)); /* * We set the cheapest-path fields immediately, just in case they were diff --git a/src/backend/optimizer/path/appendorpath.c b/src/backend/optimizer/path/appendorpath.c new file mode 100644 index 00000000000..37b90e48701 --- /dev/null +++ b/src/backend/optimizer/path/appendorpath.c @@ -0,0 +1,1061 @@ +/* + * support Append plan for ORed clauses + * Teodor Sigaev <teodor@sigaev.ru> + */ +#include "postgres.h" + +#include "access/skey.h" +#include "catalog/pg_am.h" +#include "optimizer/cost.h" +#include "optimizer/clauses.h" +#include "optimizer/optimizer.h" +#include "optimizer/paths.h" +#include "optimizer/pathnode.h" +#include "optimizer/planmain.h" +#include "optimizer/restrictinfo.h" +#include "utils/lsyscache.h" + +typedef struct CKey { + RestrictInfo *rinfo; /* original rinfo */ + int n; /* IndexPath's number in bitmapquals */ + OpExpr *normalizedexpr; /* expression with Var on left */ + Var *var; + Node *value; + Oid opfamily; + int strategy; + uint8 strategyMask; +} CKey; +#define BTMASK(x) ( 1<<(x) ) + +static List* find_common_quals( BitmapOrPath *path ); +static RestrictInfo* unionOperation(CKey *key); +static BitmapOrPath* cleanup_nested_quals( PlannerInfo *root, RelOptInfo *rel, BitmapOrPath *path ); +static List* sortIndexScans( List* ipaths ); +static List* reverseScanDirIdxPaths(List *indexPaths); +static IndexPath* reverseScanDirIdxPath(IndexPath *ipath); +static bool checkSameIndex(Path *path, Oid *indexoid); + +#define IS_LESS(a) ( (a) == BTLessStrategyNumber || (a)== BTLessEqualStrategyNumber ) +#define IS_GREATER(a) ( (a) == BTGreaterStrategyNumber || (a) == BTGreaterEqualStrategyNumber ) +#define IS_ONE_DIRECTION(a,b) ( \ + ( IS_LESS(a) && IS_LESS(b) ) \ + || \ + ( IS_GREATER(a) && IS_GREATER(b) ) \ +) + +typedef struct ExExpr { + OpExpr *expr; + Oid opfamily; + Oid lefttype; + Oid righttype; + int strategy; + int attno; +} ExExpr; + + +typedef struct IndexPathEx { + IndexPath *path; + List *preparedquals; /* list of ExExpr */ +} IndexPathEx; + +static List* +clauses_get_exprs(List *listIndexClause) { + ListCell *i, *c; + List *exprs=NULL; + + foreach(i, listIndexClause) + { + IndexClause *ic = lfirst(i); + + foreach(c, ic->indexquals) + { + RestrictInfo *rinfo = lfirst(c); + OpExpr *expr = (OpExpr*)rinfo->clause; + + exprs = lappend(exprs, expr); + } + } + + return exprs; +} + + +/*---------- + * keybased_rewrite_or_index_quals + * Examine join OR-of-AND quals to see if any useful common restriction + * clauses can be extracted. If so, try to use for creating new index paths. + * + * For example consider + * WHERE ( a.x=5 and a.y>10 ) OR a.x>5 + * and there is an index on a.x or (a.x, a.y). So, plan + * will be seqscan or BitmapOr(IndexPath,IndexPath) + * So, we can add some restriction: + * WHERE (( a.x=5 and a.y>10 ) OR a.x>5) AND a.x>=5 + * and plan may be so + * Index Scan (a.x>=5) + * Filter( (( a.x=5 and a.y>10 ) OR a.x>5) ) + * + * We don't want to add new clauses to baserestrictinfo, just + * use it as index quals. + * + * Next thing which it possible to test is use append of + * searches instead of OR. + * For example consider + * WHERE ( a.x=5 and a.y>10 ) OR a.x>6 + * and there is an index on (a.x) (a.x, a.y) + * So, we can suggest follow plan: + * Append + * Filter ( a.x=5 and a.y>10 ) OR (a.x>6) + * Index Scan (a.x=5) --in case of index on (a.x) + * Index Scan (a.x>6) + * For that we should proof that index quals isn't overlapped, + * also, some index quals may be containedi in other, so it can be eliminated + */ + +void +keybased_rewrite_index_paths(PlannerInfo *root, RelOptInfo *rel) +{ + BitmapOrPath *bestpath = NULL; + ListCell *i; + List *commonquals; + AppendPath *appendidxpath; + List *indexPaths; + IndexOptInfo *index; + + foreach(i, rel->baserestrictinfo) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(i); + + if (restriction_is_or_clause(rinfo) && + !rinfo->outerjoin_delayed) + { + /* + * Use the generate_bitmap_or_paths() machinery to estimate the + * value of each OR clause. We can use regular restriction + * clauses along with the OR clause contents to generate + * indexquals. We pass outer_rel = NULL so that sub-clauses + * that are actually joins will be ignored. + */ + List *orpaths; + ListCell *k; + + orpaths = generate_bitmap_or_paths(root, rel, + list_make1(rinfo), + rel->baserestrictinfo); + + /* Locate the cheapest OR path */ + foreach(k, orpaths) + { + BitmapOrPath *path = (BitmapOrPath *) lfirst(k); + Oid indexoid = InvalidOid; + + Assert(IsA(path, BitmapOrPath)); + + if (checkSameIndex((Path*)path, &indexoid) == false) + continue; + + if (bestpath == NULL || + path->path.total_cost < bestpath->path.total_cost) + { + bestpath = path; + } + } + } + } + + /* Fail if no suitable clauses found */ + if (bestpath == NULL) + return; + + commonquals = find_common_quals(bestpath); + /* Found quals with the same args, but with, may be, different + operations */ + if ( commonquals != NULL ) { + List *addon=NIL; + + foreach(i, commonquals) { + CKey *key = (CKey*)lfirst(i); + RestrictInfo *rinfo; + + /* + * get 'union' of operation for key + */ + rinfo = unionOperation(key); + if ( rinfo ) + addon = lappend(addon, rinfo); + } + + /* + * Ok, we found common quals and union it, so we will try to + * create new possible index paths + */ + if ( addon ) { + List *origbaserestrictinfo = list_copy(rel->baserestrictinfo); + + rel->baserestrictinfo = list_concat(rel->baserestrictinfo, addon); + + create_index_paths(root, rel); + + rel->baserestrictinfo = origbaserestrictinfo; + } + } + + /* + * Check if indexquals isn't overlapped and all index scan + * are on the same index. + */ + if ( (bestpath = cleanup_nested_quals( root, rel, bestpath )) == NULL ) + return; + + if (IsA(bestpath, IndexPath)) { + IndexPath *ipath = (IndexPath*)bestpath; + + /* + * It's possible to do only one index scan :) + */ + index = ipath->indexinfo; + + if ( root->query_pathkeys != NIL && index->sortopfamily && OidIsValid(index->sortopfamily[0]) ) + { + List *pathkeys; + + pathkeys = build_index_pathkeys(root, index, + ForwardScanDirection); + pathkeys = truncate_useless_pathkeys(root, rel, + pathkeys); + + ipath->path.pathkeys = pathkeys; + add_path(rel, (Path *) ipath); + + /* + * add path ordered in backward direction if our pathkeys + * is still unusable... + */ + if ( pathkeys == NULL || pathkeys_useful_for_ordering(root, pathkeys) == 0 ) + { + pathkeys = build_index_pathkeys(root, index, + BackwardScanDirection); + pathkeys = truncate_useless_pathkeys(root, rel, + pathkeys); + + ipath = reverseScanDirIdxPath( ipath ); + + ipath->path.pathkeys = pathkeys; + add_path(rel, (Path *) ipath); + } + } else + add_path(rel, (Path *) ipath); + return; + } + + /* recount costs */ + foreach(i, bestpath->bitmapquals ) { + IndexPath *ipath = (IndexPath*)lfirst(i); + + Assert( IsA(ipath, IndexPath) ); + ipath->path.rows = rel->tuples * clauselist_selectivity(root, + clauses_get_exprs(ipath->indexclauses), + rel->relid, + JOIN_INNER, + NULL); + ipath->path.rows = clamp_row_est(ipath->path.rows); + cost_index(ipath, root, 1, false); + } + + /* + * Check if append index can suggest ordering of result + * + * Also, we should say to AppendPath about targetlist: + * target list will be taked from indexscan + */ + index = ((IndexPath*)linitial(bestpath->bitmapquals))->indexinfo; + if ( root->query_pathkeys != NIL && index->sortopfamily && OidIsValid(index->sortopfamily[0]) && + (indexPaths = sortIndexScans( bestpath->bitmapquals )) !=NULL ) { + List *pathkeys; + + pathkeys = build_index_pathkeys(root, index, + ForwardScanDirection); + pathkeys = truncate_useless_pathkeys(root, rel, + pathkeys); + + appendidxpath = create_append_path(root, rel, indexPaths, NIL, pathkeys, NULL, 0, + false, NIL, -1.0, true); + add_path(rel, (Path *) appendidxpath); + + /* + * add path ordered in backward direction if our pathkeys + * is still unusable... + */ + if ( pathkeys == NULL || pathkeys_useful_for_ordering(root, pathkeys) == 0 ) { + + pathkeys = build_index_pathkeys(root, index, + BackwardScanDirection); + pathkeys = truncate_useless_pathkeys(root, rel, + pathkeys); + + indexPaths = reverseScanDirIdxPaths(indexPaths); + appendidxpath = create_append_path(root, rel, indexPaths, NIL, + pathkeys, NULL, + 0, false, NIL, -1.0, + true); + add_path(rel, (Path *) appendidxpath); + } + } else { + appendidxpath = create_append_path(root, rel, bestpath->bitmapquals, + NIL, NIL, NULL, + 0, false, NIL, -1.0, true); + add_path(rel, (Path *) appendidxpath); + } +} + +/* + * returns true if all indexscan below uses the same index + */ +static bool +checkSameIndex(Path *path, Oid *indexoid) { + ListCell *i; + List *subpaths; + + if (IsA(path, IndexPath)) + { + IndexPath *indpath = (IndexPath*)path; + + if (indpath->indexinfo->relam != BTREE_AM_OID) + return false; + + if (*indexoid == InvalidOid) + *indexoid = indpath->indexinfo->indexoid; + else if (*indexoid != indpath->indexinfo->indexoid) + return false; + + return true; + } + else if (IsA(path, BitmapOrPath)) + { + BitmapOrPath *orpath = (BitmapOrPath*)path; + + subpaths = orpath->bitmapquals; + + } + else if (IsA(path, BitmapAndPath)) + { + BitmapAndPath *andpath = (BitmapAndPath*)path; + + subpaths = andpath->bitmapquals; + } + else + { + elog(ERROR, "unexpected path type: %d", nodeTag(path)); + } + + Assert(list_length(subpaths) > 0); + + foreach(i, subpaths) + { + Path *subpath = (Path *) lfirst(i); + + if (checkSameIndex(subpath, indexoid) == false) + return false; + } + + return true; +} + +/* + * transformToCkey - transform RestrictionInfo + * to CKey struct. Fucntion checks possibility and correctness of + * RestrictionInfo to use it as common key, normalizes + * expression and "caches" some information. Note, + * original RestrictInfo isn't touched + */ + +static CKey* +transformToCkey( IndexOptInfo *index, RestrictInfo* rinfo, int indexcol) { + CKey *key; + OpExpr *expr = (OpExpr*)rinfo->clause; + + if ( rinfo->outerjoin_delayed ) + return NULL; + + if ( !IsA(expr, OpExpr) ) + return NULL; + + if ( contain_mutable_functions((Node*)expr) ) + return NULL; + + if ( list_length( expr->args ) != 2 ) + return NULL; + + key = (CKey*)palloc(sizeof(CKey)); + key->rinfo = rinfo; + + key->normalizedexpr = (OpExpr*)copyObject( expr ); + if (!bms_equal(rinfo->left_relids, index->rel->relids)) + CommuteOpExpr(key->normalizedexpr); + + /* + * fix_indexqual_operand returns copy of object + */ + key->var = (Var*)fix_indexqual_operand(linitial(key->normalizedexpr->args), index, indexcol); + Assert( IsA(key->var, Var) ); + + key->opfamily = index->opfamily[ key->var->varattno - 1 ]; + + /* restore varattno, because it may be different in different index */ + key->var->varattno = key->var->varattnosyn; + + key->value = (Node*)lsecond(key->normalizedexpr->args); + + key->strategy = get_op_opfamily_strategy( key->normalizedexpr->opno, key->opfamily); + Assert( key->strategy != InvalidStrategy ); + + key->strategyMask = BTMASK(key->strategy); + + return key; +} + +/* + * get_index_quals - get list of quals in + * CKeys form + */ + +static List* +get_index_quals(IndexPath *path, int cnt) { + ListCell *i, *c; + List *quals = NIL; + + foreach(i, path->indexclauses) { + IndexClause *ic = lfirst(i); + + foreach(c, ic->indexquals) { + CKey *k = transformToCkey( path->indexinfo, + (RestrictInfo*)lfirst(c), + ic->indexcol); + if ( k ) { + k->n = cnt; + quals = lappend(quals, k); + } + } + } + return quals; +} + +/* + * extract all quals from bitmapquals->indexquals for + */ +static List* +find_all_quals( BitmapOrPath *path, int *counter ) { + ListCell *i,*j; + List *allquals = NIL; + + *counter = 0; + + foreach(i, path->bitmapquals ) + { + Path *subpath = (Path *) lfirst(i); + + if ( IsA(subpath, BitmapAndPath) ) { + foreach(j, ((BitmapAndPath*)subpath)->bitmapquals) { + Path *subsubpath = (Path *) lfirst(j); + + if ( IsA(subsubpath, IndexPath) ) { + if ( ((IndexPath*)subsubpath)->indexinfo->relam != BTREE_AM_OID ) + return NIL; + allquals = list_concat(allquals, get_index_quals( (IndexPath*)subsubpath, *counter )); + } else + return NIL; + } + } else if ( IsA(subpath, IndexPath) ) { + if ( ((IndexPath*)subpath)->indexinfo->relam != BTREE_AM_OID ) + return NIL; + allquals = list_concat(allquals, get_index_quals( (IndexPath*)subpath, *counter )); + } else + return NIL; + + (*counter)++; + } + + return allquals; +} + +/* + * Compares aruments of operation + */ +static bool +iseqCKeyArgs( CKey *a, CKey *b ) { + if ( a->opfamily != b->opfamily ) + return false; + + if ( !equal( a->value, b->value ) ) + return false; + + if ( !equal( a->var, b->var ) ) + return false; + + return true; +} + +/* + * Count entries of CKey with the same arguments + */ +static int +count_entry( List *allquals, CKey *tocmp ) { + ListCell *i; + int curcnt=0; + + foreach(i, allquals) { + CKey *key = lfirst(i); + + if ( key->n == curcnt ) { + continue; + } else if ( key->n == curcnt+1 ) { + if ( iseqCKeyArgs( key, tocmp ) ) { + tocmp->strategyMask |= key->strategyMask; + curcnt++; + } + } else + return -1; + } + + return curcnt+1; +} + +/* + * Finds all CKey with the same arguments + */ +static List* +find_common_quals( BitmapOrPath *path ) { + List *allquals; + List *commonquals = NIL; + ListCell *i; + int counter; + + if ( (allquals = find_all_quals( path, &counter ))==NIL ) + return NIL; + + foreach(i, allquals) { + CKey *key = lfirst(i); + + if ( key->n != 0 ) + break; + + if ( counter == count_entry(allquals, key) ) + commonquals = lappend( commonquals, key ); + } + + return commonquals; +} + +/* + * unionOperation - make RestrictInfo with combined operation + */ + +static RestrictInfo* +unionOperation(CKey *key) { + RestrictInfo *rinfo; + Oid lefttype, righttype; + int strategy; + + switch( key->strategyMask ) { + case BTMASK(BTLessStrategyNumber): + case BTMASK(BTLessEqualStrategyNumber): + case BTMASK(BTEqualStrategyNumber): + case BTMASK(BTGreaterEqualStrategyNumber): + case BTMASK(BTGreaterStrategyNumber): + /* trivial case */ + break; + case BTMASK(BTLessStrategyNumber) | BTMASK(BTLessEqualStrategyNumber): + case BTMASK(BTLessStrategyNumber) | BTMASK(BTLessEqualStrategyNumber) | BTMASK(BTEqualStrategyNumber): + case BTMASK(BTLessStrategyNumber) | BTMASK(BTEqualStrategyNumber): + case BTMASK(BTLessEqualStrategyNumber) | BTMASK(BTEqualStrategyNumber): + /* any subset of <, <=, = can be unioned with <= */ + key->strategy = BTLessEqualStrategyNumber; + break; + case BTMASK(BTGreaterEqualStrategyNumber) | BTMASK(BTGreaterStrategyNumber): + case BTMASK(BTEqualStrategyNumber) | BTMASK(BTGreaterEqualStrategyNumber) | BTMASK(BTGreaterStrategyNumber): + case BTMASK(BTEqualStrategyNumber) | BTMASK(BTGreaterStrategyNumber): + case BTMASK(BTEqualStrategyNumber) | BTMASK(BTGreaterEqualStrategyNumber): + /* any subset of >, >=, = can be unioned with >= */ + key->strategy = BTGreaterEqualStrategyNumber; + break; + default: + /* + * Can't make common restrict qual + */ + return NULL; + } + + get_op_opfamily_properties(key->normalizedexpr->opno, key->opfamily, false, + &strategy, &lefttype, &righttype); + + if ( strategy != key->strategy ) { + /* + * We should check because it's possible to have "strange" + * opfamilies - without some strategies... + */ + key->normalizedexpr->opno = get_opfamily_member(key->opfamily, lefttype, righttype, key->strategy); + + if ( key->normalizedexpr->opno == InvalidOid ) + return NULL; + + key->normalizedexpr->opfuncid = get_opcode( key->normalizedexpr->opno ); + Assert ( key->normalizedexpr->opfuncid != InvalidOid ); + } + + rinfo = make_simple_restrictinfo((Expr*)key->normalizedexpr); + + return rinfo; +} + +/* + * Remove unneeded RestrioctionInfo nodes as it + * needed by predicate_*_by() + */ +static void +make_predicate(List *indexclauses, List **preds) { + ListCell *i, *c; + + *preds = NIL; + + foreach(i, indexclauses) + { + IndexClause *ic = lfirst(i); + RestrictInfo *rinfo = ic->rinfo; + + if ( rinfo->outerjoin_delayed ) + continue; + + foreach(c, ic->indexquals) + { + RestrictInfo *rinfoq = lfirst(c); + OpExpr *expr = (OpExpr*)rinfoq->clause; + + if ( !IsA(expr, OpExpr) ) + goto end; + + if ( list_length( expr->args ) != 2 ) + goto end; + } + + *preds = lappend(*preds, ic); + +end: + continue; + } +} + +#define CELL_GET_CLAUSES(x) ( ((IndexPath*)lfirst(x))->indexclauses ) + +/* + * returns list of all nested quals + */ +static List* +contained_quals(List *nested, List* quals, ListCell *check) { + ListCell *i; + List *checkpred; + + if ( list_member_ptr( nested, lfirst(check) ) ) + return nested; + + checkpred = clauses_get_exprs(CELL_GET_CLAUSES(check)); + + if ( contain_mutable_functions((Node*)checkpred) ) + return nested; + + foreach(i, quals ) + { + if ( check == i ) + continue; + + if ( list_member_ptr( nested, lfirst(i) ) ) + continue; + + if (predicate_implied_by( checkpred, + clauses_get_exprs(CELL_GET_CLAUSES(i)), + false ) ) + nested = lappend( nested, lfirst(i) ); + } + return nested; +} + +/* + * Checks that one row can be in several quals. + * It's guaranteed by predicate_refuted_by() + */ +static bool +is_intersect(List *quals, ListCell *check) { + ListCell *i; + List *checkpred=NULL; + + checkpred=clauses_get_exprs(CELL_GET_CLAUSES(check)); + Assert( checkpred != NULL ); + + for_each_cell(i, quals, check) { + if ( i==check ) + continue; + + if ( predicate_refuted_by( checkpred, + clauses_get_exprs(CELL_GET_CLAUSES(i)), + false ) == false ) + return true; + } + + return false; +} + +/* + * Removes nested quals and gurantees that quals are not intersected, + * ie one row can't satisfy to several quals. It's open a possibility of + * Append node using instead of BitmapOr + */ +static BitmapOrPath* +cleanup_nested_quals( PlannerInfo *root, RelOptInfo *rel, BitmapOrPath *path ) { + ListCell *i; + IndexOptInfo *index=NULL; + List *nested = NULL; + + /* + * check all path to use only one index + */ + foreach(i, path->bitmapquals ) + { + + if ( IsA(lfirst(i), IndexPath) ) { + List *preds; + IndexPath *subpath = (IndexPath *) lfirst(i); + + if ( subpath->indexinfo->relam != BTREE_AM_OID ) + return NULL; + + if ( index == NULL ) + index = subpath->indexinfo; + else if ( index->indexoid != subpath->indexinfo->indexoid ) + return NULL; + + /* + * work only with optimizable quals + */ + make_predicate(subpath->indexclauses, &preds); + if (preds == NIL) + return NULL; + subpath->indexclauses = preds; + } else + return NULL; + } + + /* + * eliminate nested quals + */ + foreach(i, path->bitmapquals ) { + nested = contained_quals(nested, path->bitmapquals, i); + } + + if ( nested != NIL ) { + path->bitmapquals = list_difference_ptr( path->bitmapquals, nested ); + + Assert( list_length( path->bitmapquals )>0 ); + + /* + * All quals becomes only one after eliminating nested quals + */ + if (list_length( path->bitmapquals ) == 1) + return (BitmapOrPath*)linitial(path->bitmapquals); + } + + /* + * Checks for intersection + */ + foreach(i, path->bitmapquals ) { + if ( is_intersect( path->bitmapquals, i ) ) + return NULL; + } + + return path; +} + +/* + * Checks if whole result of one simple operation is contained + * in another + */ +static int +simpleCmpExpr( ExExpr *a, ExExpr *b ) { + if ( predicate_implied_by((List*)a->expr, (List*)b->expr, false) ) + /* + * a:( Var < 15 ) > b:( Var <= 10 ) + */ + return 1; + else if ( predicate_implied_by((List*)b->expr, (List*)a->expr, false) ) + /* + * a:( Var <= 10 ) < b:( Var < 15 ) + */ + return -1; + else + return 0; +} + +/* + * Trys to define where is equation - on left or right side + * a(< 10) b(=11) - on right + * a(> 10) b(=9) - on left + * a(= 10) b(=11) - on right + * a(= 10) b(=9) - on left + * Any other - result is 0; + */ +static int +cmpEqExpr( ExExpr *a, ExExpr *b ) { + Oid oldop = b->expr->opno; + int res=0; + + b->expr->opno = get_opfamily_member(b->opfamily, b->lefttype, b->righttype, BTLessStrategyNumber); + if ( b->expr->opno != InvalidOid ) { + b->expr->opfuncid = get_opcode( b->expr->opno ); + res = simpleCmpExpr(a,b); + } + + if ( res == 0 ) { + b->expr->opno = get_opfamily_member(b->opfamily, b->lefttype, b->righttype, BTGreaterStrategyNumber); + if ( b->expr->opno != InvalidOid ) { + b->expr->opfuncid = get_opcode( b->expr->opno ); + res = -simpleCmpExpr(a,b); + } + } + + b->expr->opno = oldop; + b->expr->opfuncid = get_opcode( b->expr->opno ); + + return res; +} + +/* + * Is result of a contained in result of b or on the contrary? + */ +static int +cmpNegCmp( ExExpr *a, ExExpr *b ) { + Oid oldop = b->expr->opno; + int res = 0; + + b->expr->opno = get_negator( b->expr->opno ); + if ( b->expr->opno != InvalidOid ) { + b->expr->opfuncid = get_opcode( b->expr->opno ); + res = simpleCmpExpr(a,b); + } + + b->expr->opno = oldop; + b->expr->opfuncid = get_opcode( b->expr->opno ); + + return ( IS_LESS(a->strategy) ) ? res : -res; +} + +/* + * Returns 1 if whole result of a is on left comparing with result of b + * Returns -1 if whole result of a is on right comparing with result of b + * Return 0 if it's impossible to define or results is overlapped + * Expressions should use the same attribute of index and should be + * a simple: just one operation with index. + */ +static int +cmpExpr( ExExpr *a, ExExpr *b ) { + int res; + + /* + * If a and b are overlapped, we can't decide which one is + * lefter or righter + */ + if ( IS_ONE_DIRECTION(a->strategy, b->strategy) || + predicate_refuted_by((List*)a->expr, (List*)b->expr, false) == false ) + return 0; + + /* + * In this place it's impossible to have a row which satisfies + * a and b expressions, so we will try to find relatiove position of that results + */ + if (a->strategy == BTEqualStrategyNumber && + b->strategy == BTEqualStrategyNumber) { + return cmpEqExpr(a, b); + } else if ( b->strategy == BTEqualStrategyNumber ) { + return -cmpEqExpr(a, b); /* Covers cases with any operations in a */ + } else if ( a->strategy == BTEqualStrategyNumber ) { + return cmpEqExpr(b, a); + } else if ( (res = cmpNegCmp(a, b)) == 0 ) { /* so, a(<10) b(>20) */ + res = -cmpNegCmp(b, a); + } + + return res; +} + +static IndexOptInfo *sortingIndex = NULL; +static bool volatile unableToDefine = false; + +/* + * Try to define positions of result which satisfy indexquals a and b per + * one index's attribute. + */ +static int +cmpColumnQuals( List *a, List *b, int attno ) { + int res = 0; + ListCell *ai, *bi; + + foreach(ai, a) { + ExExpr *ae = (ExExpr*)lfirst(ai); + + if ( attno != ae->attno ) + continue; + + foreach(bi, b) { + ExExpr *be = (ExExpr*)lfirst(bi); + + if ( attno != be->attno ) + continue; + + if ((res=cmpExpr(ae, be))!=0) + return res; + + if (res == 0 && ae->strategy == be->strategy && + be->strategy != BTEqualStrategyNumber && + equal(ae->expr, be->expr)) + { + /* + * It's impossible to get defined order for non-eq the same clauses + */ + unableToDefine = true; + PG_RE_THROW(); /* it should be PG_THROW(), but it's the same */ + } + } + } + + return 0; +} + +/* + * Compare result of two indexquals. + * Warinig: it use PG_RE_THROW(), so any call should be wrapped with + * PG_TRY(). Try/catch construction is used here for minimize unneeded + * actions when sorting is impossible + */ +static int +cmpIndexPathEx(const void *a, const void *b) { + IndexPathEx *aipe = (IndexPathEx*)a; + IndexPathEx *bipe = (IndexPathEx*)b; + int attno, res = 0; + + for(attno=1; res==0 && attno<=sortingIndex->ncolumns; attno++) + res=cmpColumnQuals(aipe->preparedquals, bipe->preparedquals, attno); + + if ( res==0 ) { + unableToDefine = true; + PG_RE_THROW(); /* it should be PG_THROW(), but it's the same */ + } + + return res; +} + +/* + * Initialize lists of operation in useful form + */ +static List* +prepareQuals(IndexOptInfo *index, List *indexclauses) { + ListCell *i, *c; + List *res=NULL; + ExExpr *ex; + + foreach(i, indexclauses) + { + IndexClause *ic = lfirst(i); + RestrictInfo *rinfo = lfirst(i); + + if ( rinfo->outerjoin_delayed ) + return NULL; + + foreach(c, ic->indexquals) + { + RestrictInfo *rinfo = lfirst(c); + OpExpr *expr = (OpExpr*)rinfo->clause; + + if ( !IsA(expr, OpExpr) ) + return NULL; + + if ( list_length( expr->args ) != 2 ) + return NULL; + + if ( contain_mutable_functions((Node*)expr) ) + return NULL; + + ex = (ExExpr*)palloc(sizeof(ExExpr)); + ex->expr = (OpExpr*)copyObject( expr ); + if (!bms_equal(rinfo->left_relids, index->rel->relids)) + CommuteOpExpr(ex->expr); + linitial(ex->expr->args) = fix_indexqual_operand(linitial(ex->expr->args), index, ic->indexcol); + ex->attno = ((Var*)linitial(ex->expr->args))->varattno; + ex->opfamily = index->opfamily[ ex->attno - 1 ]; + get_op_opfamily_properties( ex->expr->opno, ex->opfamily, false, + &ex->strategy, &ex->lefttype, &ex->righttype); + + res = lappend(res, ex); + } + } + + return res; +} + +/* + * sortIndexScans - sorts index scans to get sorted results. + * Function supposed that index is the same for all + * index scans + */ +static List* +sortIndexScans( List* ipaths ) { + ListCell *i; + int j=0; + IndexPathEx *ipe = (IndexPathEx*)palloc( sizeof(IndexPathEx)*list_length(ipaths) ); + List *orderedPaths = NIL; + IndexOptInfo *index = ((IndexPath*)linitial(ipaths))->indexinfo; + + foreach(i, ipaths) { + ipe[j].path = (IndexPath*)lfirst(i); + ipe[j].preparedquals = prepareQuals(index, ipe[j].path->indexclauses); + + if (ipe[j].preparedquals == NULL) + return NULL; + j++; + } + + sortingIndex = index; + unableToDefine = false; + PG_TRY(); { + qsort(ipe, list_length(ipaths), sizeof(IndexPathEx), cmpIndexPathEx); + } PG_CATCH(); { + if ( unableToDefine == false ) + PG_RE_THROW(); /* not our problem */ + } PG_END_TRY(); + + if ( unableToDefine == true ) + return NULL; + + for(j=0;j<list_length(ipaths);j++) + orderedPaths = lappend(orderedPaths, ipe[j].path); + + return orderedPaths; +} + +static IndexPath* +reverseScanDirIdxPath(IndexPath *ipath) { + IndexPath *n = makeNode(IndexPath); + + *n = *ipath; + + n->indexscandir = BackwardScanDirection; + + return n; +} + +static List* +reverseScanDirIdxPaths(List *indexPaths) { + List *idxpath = NIL; + ListCell *i; + + foreach(i, indexPaths) { + idxpath = lcons(reverseScanDirIdxPath( (IndexPath*)lfirst(i) ), idxpath); + } + + return idxpath; +} diff --git a/src/backend/optimizer/path/clausesel.c b/src/backend/optimizer/path/clausesel.c index 79d8f761ab2..ac48152da41 100644 --- a/src/backend/optimizer/path/clausesel.c +++ b/src/backend/optimizer/path/clausesel.c @@ -12,8 +12,14 @@ * *------------------------------------------------------------------------- */ +#include <math.h> #include "postgres.h" +#include "access/genam.h" +#include "access/htup_details.h" +#include "catalog/pg_collation.h" +#include "commands/vacuum.h" +#include "funcapi.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" #include "optimizer/clauses.h" @@ -26,6 +32,16 @@ #include "utils/lsyscache.h" #include "utils/selfuncs.h" +#include "parser/parsetree.h" +#include "utils/array.h" +#include "utils/builtins.h" +#include "utils/rel.h" +#include "utils/syscache.h" +#include "utils/typcache.h" + +#define EXHAUSTIVE_IN_SELECTIVITY_THRESHOLD (default_statistics_target/4) +#define RANGE_IN_SELECTIVITY_THRESHOLD (default_statistics_target/20) + /* source-code-compatibility hacks for pull_varnos() API change */ #define NumRelids(a,b) NumRelids_new(a,b) @@ -47,6 +63,1048 @@ static void addRangeClause(RangeQueryClause **rqlist, Node *clause, bool varonleft, bool isLTsel, Selectivity s2); static RelOptInfo *find_single_rel_for_clauses(PlannerInfo *root, List *clauses); +static bool treat_as_join_clause(PlannerInfo *root, Node *clause, RestrictInfo *rinfo, + int varRelid, SpecialJoinInfo *sjinfo); + +typedef enum CorrelationKind { + CKRestrict = 0, + CKIndepend, /* unknown correlation */ + CKLikelySelf, /* Seems, should be close to be correlated, like agg with + self join */ + CKSelf, /* 100% correlation because of self join */ + CKMul /* product of all CKLikelySelf * CKSelf */ +} CorrelationKind; +static CorrelationKind get_correlation_kind(PlannerInfo *root, int varRelid, + OpExpr* expr); + +static void +clauselist_selectivity_simple_ext(PlannerInfo *root, + List *clauses, + int varRelid, + JoinType jointype, + SpecialJoinInfo *sjinfo, + Bitmapset *estimatedclauses, + Selectivity s[5 /* per CorrelationKind */]); +/* + * Get variabe node. Returns null if node is not a Var node. + */ +static inline Var* +get_var(Node* node) +{ + if (IsA(node, RelabelType)) + node = (Node *) ((RelabelType *) node)->arg; + + return IsA(node, Var) ? (Var*)node : NULL; +} + +/* + * Locate compound index which can be used for multicolumn clauses/join. + */ +static IndexOptInfo* +locate_inner_multicolumn_index(PlannerInfo *root, Index varno, List* vars, + int n_clauses, + int **permutation, List **missed_vars, int* n_keys) +{ + ListCell *ilist; + RelOptInfo *rel = find_base_rel(root, varno); + IndexOptInfo *index_opt = NULL; + List *missed_vars_opt = NIL; + int *permutation_opt = NULL; + int n_index_cols_opt = 0; + bool used[INDEX_MAX_KEYS]; + int posvars[INDEX_MAX_KEYS]; + + *n_keys = 0; + *missed_vars = NIL; + + Assert(list_length(vars) >= 1); + Assert(list_length(vars) <= n_clauses); + + foreach(ilist, rel->indexlist) + { + IndexOptInfo *index = (IndexOptInfo *) lfirst(ilist); + ListCell *vlist; + int i, n_index_cols = 0; + List *missed = NIL; + int *perm = NULL; + + 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, DEFAULT_COLLATION_OID, vardata, + constant, false, true, false, + n_keys); + + if (usedEqSel) + *usedEqSel = true; + } + } + else + { + s = eqconst_selectivity(typentry->eq_opr, DEFAULT_COLLATION_OID, vardata, + constant, false, true, false, + -1); + } + + sum += s - s*sum; + } + +finish: + if (it.isExhaustive) + sum *= ((double)(it.n_variants))/EXHAUSTIVE_IN_SELECTIVITY_THRESHOLD; + + return sum; +} + +typedef struct ClauseVarPair +{ + Var *var; + int idx; +} ClauseVarPair; + +static void +appendCVP(List **cvp, Var *var, int idx) +{ + ClauseVarPair *e; + + e = palloc(sizeof(*e)); + e->var = var; + e->idx = idx; + + *cvp = lappend(*cvp, e); +} + +static bool +initVarData(IndexOptInfo *index, VariableStatData *vardata) +{ + Relation indexRel = index_open(index->indexoid, AccessShareLock); + + if (!indexRel->rd_rel->reltype) + { + index_close(indexRel, AccessShareLock); + + return false; + } + + memset(vardata, 0, sizeof(*vardata)); + vardata->isunique = index->unique; + vardata->atttype = indexRel->rd_rel->reltype; + vardata->rel = index->rel; + vardata->acl_ok = true; + vardata->statsTuple = SearchSysCache3(STATRELATTINH, + ObjectIdGetDatum(index->indexoid), + Int16GetDatum(1), + BoolGetDatum(false)); + vardata->freefunc = ReleaseSysCache; + + index_close(indexRel, AccessShareLock); + + if (!HeapTupleIsValid(vardata->statsTuple)) + { + ReleaseVariableStats(*vardata); + return false; + } + + vardata->sslots = index->sslots; + + return true; +} + +static int +markEstimatedColumns(Bitmapset **estimatedclauses, List *pairs, + List *vars, List *missed_vars) +{ + ListCell *l; + int n_estimated = 0; + + foreach(l, vars) + { + Var* var = (Var *) lfirst(l); + ListCell *ll; + + if (list_member_ptr(missed_vars, var)) + continue; + + foreach(ll, pairs) + { + ClauseVarPair *cvp=(ClauseVarPair*)lfirst(ll); + + if (cvp->var == var) + { + *estimatedclauses = bms_add_member(*estimatedclauses, cvp->idx); + n_estimated += 1; + break; + } + } + + Assert(ll != NULL); + } + + return n_estimated; +} + +#define SET_VARNOS(vn) do { \ + if ((vn) != 0) \ + { \ + if (data[0].varno == 0) \ + data[0].varno = (vn); \ + else if (data[1].varno == 0 && data[0].varno != (vn)) \ + data[1].varno = (vn); \ + } \ +} while(0) + +#define GET_RELBY_NO(vn) \ +((data[0].varno == (vn) && (vn) != 0) ? &data[0] : ((data[1].varno == (vn) && (vn) != 0) ? &data[1] : NULL)) + +#define SET_CURDATA(vn) ((cur = GET_RELBY_NO(vn)) != NULL) + +static bool +hasSAOPRestriction(List *clauses, Bitmapset *estimatedclauses) +{ + ListCell *l; + int i = -1; + + foreach(l, clauses) + { + Node* clause = (Node *) lfirst(l); + RestrictInfo *rinfo = NULL; + + i++; + if (bms_is_member(i, estimatedclauses)) + continue; + + if (IsA(clause, RestrictInfo)) + { + rinfo = (RestrictInfo *) clause; + if (!rinfo->orclause) + clause = (Node*)rinfo->clause; + } + + if (IsA(clause, ScalarArrayOpExpr)) + return true; + } + + return false; +} + +/* + * Check if clauses represent multicolumn join with compound indexes available + * for both side of comparison of indexed columns of one relation with constant + * values. If so, calculates selectivity of compound type comparison and returns + * true. + */ +static bool +use_multicolumn_statistic(PlannerInfo *root, List *clauses, int varRelid, + JoinType jointype, SpecialJoinInfo *sjinfo, + Selectivity* restrict_selectivity, Selectivity *join_selectivity, + Bitmapset **estimatedclauses, CorrelationKind + *correlationKind) +{ + ListCell *l; + List* var_clause_map = NIL; + List* missed_vars = NIL; + int i; + int *permutation = NULL; + int n_estimated = 0; + int n_keys; + TypeCacheEntry *typentry; + + struct { + Index varno; + + List *restrictionColumns; + List *restrictionConsts; + List *in_clauses; + List *ineqRestrictionClauses; + + List *joinColumns; + + IndexOptInfo *index; + VariableStatData vardata; + } data[2], *cur; + + if (list_length(clauses) < 1) + return false; + + /* + * For simple queries default estimator is good enough, but multicolumn + * statistic could be too expensive because of search and decompress a lot + * of stat data (histogramm of multicolumn indexes). + */ + if (root->join_rel_list == NIL && root->parent_root == NULL && + root->simple_rel_array_size <= 2 /* 0th is always empty */ && + /* list_length(clauses) < 4 && */ + hasSAOPRestriction(clauses, *estimatedclauses) == false) + return false; + + *correlationKind = CKIndepend; + memset(data, 0, sizeof(data)); + + i=-1; + foreach(l, clauses) + { + Node* clause = (Node *) lfirst(l); + RestrictInfo* rinfo = NULL; + OpExpr *opclause = NULL; + + i++; + + /* do not use already estimated clauses */ + if (bms_is_member(i, *estimatedclauses)) + continue; + + if (IsA(clause, RestrictInfo)) + { + rinfo = (RestrictInfo *) clause; + if (!rinfo->orclause) + clause = (Node*)rinfo->clause; + } + if (IsA(clause, OpExpr)) + opclause = (OpExpr*)clause; + + if (IsA(clause, Var)) /* boolean variable */ + { + Var* var1 = (Var*)clause; + + SET_VARNOS(var1->varno); + if (SET_CURDATA(var1->varno)) + { + cur->restrictionColumns = lappend(cur->restrictionColumns, var1); + appendCVP(&var_clause_map, var1, i); + cur->restrictionConsts = lappend(cur->restrictionConsts, + makeBoolConst(true, false)); + } + } + else if (IsA(clause, BoolExpr) && ((BoolExpr*)clause)->boolop == NOT_EXPR) /* (NOT bool_expr) */ + { + Node* arg1 = (Node*) linitial( ((BoolExpr*)clause)->args); + Var* var1 = get_var(arg1); + + if (var1 == NULL) + continue; + + SET_VARNOS(var1->varno); + if (SET_CURDATA(var1->varno)) + { + cur->restrictionColumns = lappend(cur->restrictionColumns, var1); + appendCVP(&var_clause_map, var1, i); + cur->restrictionConsts = lappend(cur->restrictionConsts, + makeBoolConst(false, false)); + } + } + else if (IsA(clause, ScalarArrayOpExpr)) + { + ScalarArrayOpExpr* in = (ScalarArrayOpExpr*)clause; + Var* var1; + Node* arg2; + InArrayClause* iac; + + var1 = get_var((Node*)linitial(in->args)); + arg2 = (Node*) lsecond(in->args); + + if (!in->useOr + || list_length(in->args) != 2 + || get_oprrest(in->opno) != F_EQSEL + || var1 == NULL + || !IsA(arg2, Const)) + { + continue; + } + + SET_VARNOS(var1->varno); + if (SET_CURDATA(var1->varno)) + { + cur->restrictionColumns = lappend(cur->restrictionColumns, var1); + appendCVP(&var_clause_map, var1, i); + cur->restrictionConsts = lappend(cur->restrictionConsts, arg2); + + iac = (InArrayClause*)palloc(sizeof(InArrayClause)); + iac->array = (ArrayType*)DatumGetPointer(((Const*)arg2)->constvalue); + iac->index = list_length(cur->restrictionConsts) - 1; + + cur->in_clauses = lappend(cur->in_clauses, iac); + } + } + else if (opclause + && list_length(opclause->args) == 2) + { + int oprrest = get_oprrest(opclause->opno); + Node* arg1 = (Node*) linitial(opclause->args); + Node* arg2 = (Node*) lsecond(opclause->args); + Var* var1 = get_var(arg1); + Var* var2 = get_var(arg2); + + if (oprrest == F_EQSEL && treat_as_join_clause(root, (Node*)opclause, NULL, varRelid, sjinfo)) + { + if (var1 == NULL || var2 == NULL || var1->vartype != var2->vartype) + continue; + + SET_VARNOS(var1->varno); + SET_VARNOS(var2->varno); + + if (var1->varno == data[0].varno && var2->varno == data[1].varno) + { + data[0].joinColumns = lappend(data[0].joinColumns, var1); + appendCVP(&var_clause_map, var1, i); + data[1].joinColumns = lappend(data[1].joinColumns, var2); + appendCVP(&var_clause_map, var2, i); + } + else if (var1->varno == data[1].varno && var2->varno == data[0].varno) + { + data[0].joinColumns = lappend(data[0].joinColumns, var2); + appendCVP(&var_clause_map, var2, i); + data[1].joinColumns = lappend(data[1].joinColumns, var1); + appendCVP(&var_clause_map, var1, i); + } + } + else /* Estimate selectivity for a restriction clause. */ + { + /* + * Give up if it is not equality comparison of variable with + * constant or some other clause is treated as join condition + */ + if (((var1 == NULL) == (var2 == NULL))) + continue; + + if (var1 == NULL) + { + /* swap var1 and var2 */ + var1 = var2; + arg2 = arg1; + } + + SET_VARNOS(var1->varno); + + if (SET_CURDATA(var1->varno)) + { + if ((rinfo && is_pseudo_constant_clause_relids(arg2, rinfo->right_relids)) + || (!rinfo && NumRelids(root, clause) == 1 && is_pseudo_constant_clause(arg2))) + { + /* Restriction clause with a pseudoconstant . */ + Node* const_val = estimate_expression_value(root, arg2); + + if (IsA(const_val, Const)) + { + switch (oprrest) + { + case F_EQSEL: + cur->restrictionColumns = + lappend(cur->restrictionColumns, var1); + cur->restrictionConsts = + lappend(cur->restrictionConsts, const_val); + appendCVP(&var_clause_map, var1, i); + break; + case F_SCALARGTSEL: + case F_SCALARGESEL: + case F_SCALARLTSEL: + case F_SCALARLESEL: + /* + * We do not consider range predicates now, + * but we can mark them as estimated + * if their variables are covered by index. + */ + appendCVP(&var_clause_map, var1, i); + cur->ineqRestrictionClauses = + lappend(cur->ineqRestrictionClauses, var1); + break; + default: + break; + } + } + } + + } + } + } + /* else just skip clause to work with it later in caller */ + } + + *restrict_selectivity = 1.0; + *join_selectivity = 1.0; + + /* + * First, try to estimate selectivity by restrictions + */ + for(i=0; i<lengthof(data); i++) + { + cur = &data[i]; + + /* compute restriction clauses if applicable */ + if (cur->varno == 0 || list_length(cur->restrictionColumns) < 1) + continue; + + cur->index = locate_inner_multicolumn_index( + root, cur->varno, cur->restrictionColumns, + list_length(clauses), &permutation, &missed_vars, &n_keys); + + if (cur->index && n_keys > 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, + DEFAULT_COLLATION_OID, + &data[0].vardata, &data[1].vardata, + sjinfo, n_keys); + + /* for self join */ + if (data[0].index->indexoid == data[1].index->indexoid) + *correlationKind = CKSelf; + else + { + RangeTblEntry *lrte = planner_rt_fetch(data[0].index->rel->relid, root), + *rrte = planner_rt_fetch(data[1].index->rel->relid, root); + + if (lrte->relid == rrte->relid) + *correlationKind = CKSelf; + } + + for (i = 0; i < lengthof(data); i++) + ReleaseVariableStats(data[i].vardata); + + n_estimated += + markEstimatedColumns(estimatedclauses, var_clause_map, + data[0].joinColumns, missed_vars); + +cleanup: + if (permutation) + pfree(permutation); + + return n_estimated != 0; +} /**************************************************************************** * ROUTINES TO COMPUTE SELECTIVITIES @@ -68,6 +1126,54 @@ static RelOptInfo *find_single_rel_for_clauses(PlannerInfo *root, * for individual columns. This is done by simply passing the clauses to * clauselist_selectivity_simple. */ + +static void +appendSelectivityRes(Selectivity s[5], Selectivity sel, CorrelationKind ck) +{ + switch(ck) + { + case CKRestrict: + s[ck] *= sel; + break; + case CKSelf: + case CKLikelySelf: + s[CKMul] *= sel; + if (s[ck] > sel) + s[ck] = sel; + /* FALLTHROUGH */ + case CKIndepend: + s[CKIndepend] *= sel; + break; + default: + elog(ERROR, "unknown selectivity kind: %d", ck); + } +} + +static Selectivity +finalizeSelectivityRes(Selectivity s[5]) +{ + Selectivity sel; + + sel = s[CKRestrict] * s[CKIndepend]; + + if (s[CKIndepend] != s[CKMul]) + { + /* we have both independ and correlated - fallback */ + sel *= s[CKMul]; + } + else + { + /* we have only correlated join clauses */ + if (s[CKLikelySelf] != 1.0 && sel < s[CKLikelySelf]) + sel = sel + (s[CKLikelySelf] - sel) * 0.25; + + if (s[CKSelf] != 1.0 && sel < s[CKSelf]) + sel = sel + (s[CKSelf] - sel) * 1.0; + } + + return sel; +} + Selectivity clauselist_selectivity(PlannerInfo *root, List *clauses, @@ -75,9 +1181,11 @@ clauselist_selectivity(PlannerInfo *root, JoinType jointype, SpecialJoinInfo *sjinfo) { - Selectivity s1 = 1.0; + Selectivity s[5 /* per CorrelationKind */] = {1.0, 1.0, 1.0, 1.0, 1.0}; + Selectivity s2 = 1.0, s3 = 1.0; RelOptInfo *rel; Bitmapset *estimatedclauses = NULL; + CorrelationKind ck; /* * Determine if these clauses reference a single relation. If so, and if @@ -93,18 +1201,34 @@ clauselist_selectivity(PlannerInfo *root, * clauses that we've estimated using extended statistics, and that * should be ignored. */ - s1 *= statext_clauselist_selectivity(root, clauses, varRelid, - jointype, sjinfo, rel, - &estimatedclauses); + s2 = statext_clauselist_selectivity(root, clauses, varRelid, + jointype, sjinfo, rel, + &estimatedclauses); + appendSelectivityRes(s, s2, CKRestrict); + } + + /* + * Check if join conjuncts corresponds to some compound indexes on left and + * right joined relations or indexed columns of one relation is compared + * with constant values. In this case selectivity of join can be calculated + * based on statistic of this compound index. + */ + while(use_multicolumn_statistic(root, clauses, varRelid, jointype, sjinfo, + &s2, &s3, &estimatedclauses, &ck)) + { + appendSelectivityRes(s, s2, CKRestrict); + appendSelectivityRes(s, s3, ck); } /* * Apply normal selectivity estimates for the remaining clauses, passing * 'estimatedclauses' so that it skips already estimated ones. */ - return s1 * clauselist_selectivity_simple(root, clauses, varRelid, - jointype, sjinfo, - estimatedclauses); + clauselist_selectivity_simple_ext(root, clauses, varRelid, + jointype, sjinfo, + estimatedclauses, s); + + return finalizeSelectivityRes(s); } /* @@ -157,24 +1281,32 @@ clauselist_selectivity_simple(PlannerInfo *root, SpecialJoinInfo *sjinfo, Bitmapset *estimatedclauses) { - Selectivity s1 = 1.0; + Selectivity s[5 /* per CorrelationKind */] = {1.0, 1.0, 1.0, 1.0, 1.0}; + + clauselist_selectivity_simple_ext(root, clauses, varRelid, + jointype, sjinfo, + estimatedclauses, s); + + return finalizeSelectivityRes(s); +} + +static void +clauselist_selectivity_simple_ext(PlannerInfo *root, + List *clauses, + int varRelid, + JoinType jointype, + SpecialJoinInfo *sjinfo, + Bitmapset *estimatedclauses, + Selectivity s[5 /* per CorrelationKind */]) +{ RangeQueryClause *rqlist = NULL; + CorrelationKind ck; ListCell *l; int listidx; - /* - * If there's exactly one clause (and it was not estimated yet), just go - * directly to clause_selectivity(). None of what we might do below is - * relevant. - */ - if ((list_length(clauses) == 1) && - bms_num_members(estimatedclauses) == 0) - return clause_selectivity(root, (Node *) linitial(clauses), - varRelid, jointype, sjinfo); - /* * Anything that doesn't look like a potential rangequery clause gets - * multiplied into s1 and forgotten. Anything that does gets inserted into + * multiplied into s and forgotten. Anything that does gets inserted into * an rqlist entry. */ listidx = -1; @@ -207,7 +1339,7 @@ clauselist_selectivity_simple(PlannerInfo *root, rinfo = (RestrictInfo *) clause; if (rinfo->pseudoconstant) { - s1 = s1 * s2; + appendSelectivityRes(s, s2, CKRestrict); continue; } clause = (Node *) rinfo->clause; @@ -221,12 +1353,17 @@ clauselist_selectivity_simple(PlannerInfo *root, * the simple way we are expecting.) Most of the tests here can be * done more efficiently with rinfo than without. */ + ck = treat_as_join_clause(root, clause, rinfo, varRelid, sjinfo) ? + CKIndepend : CKRestrict; if (is_opclause(clause) && list_length(((OpExpr *) clause)->args) == 2) { OpExpr *expr = (OpExpr *) clause; bool varonleft = true; bool ok; + if (ck == CKIndepend) + ck = get_correlation_kind(root, varRelid, expr); + if (rinfo) { ok = (bms_membership(rinfo->clause_relids) == BMS_SINGLETON) && @@ -265,7 +1402,7 @@ clauselist_selectivity_simple(PlannerInfo *root, break; default: /* Just merge the selectivity in generically */ - s1 = s1 * s2; + appendSelectivityRes(s, s2, ck); break; } continue; /* drop to loop bottom */ @@ -273,7 +1410,7 @@ clauselist_selectivity_simple(PlannerInfo *root, } /* Not the right form, so treat it generically. */ - s1 = s1 * s2; + appendSelectivityRes(s, s2, ck); } /* @@ -335,23 +1472,19 @@ clauselist_selectivity_simple(PlannerInfo *root, } } /* Merge in the selectivity of the pair of clauses */ - s1 *= s2; + appendSelectivityRes(s, s2, CKRestrict); } else { /* Only found one of a pair, merge it in generically */ - if (rqlist->have_lobound) - s1 *= rqlist->lobound; - else - s1 *= rqlist->hibound; + appendSelectivityRes(s, (rqlist->have_lobound) ? rqlist->lobound : + rqlist->hibound, CKRestrict); } /* release storage and advance */ rqnext = rqlist->next; pfree(rqlist); rqlist = rqnext; } - - return s1; } /* @@ -560,6 +1693,137 @@ treat_as_join_clause(PlannerInfo *root, Node *clause, RestrictInfo *rinfo, } } +typedef struct RangeTblEntryContext { + RangeTblEntry *rte; + int count; +} RangeTblEntryContext; + +static bool +find_rte_walker(Node *node, RangeTblEntryContext *context) +{ + if (node == NULL) + return false; + + if (context->count > 1) + return true; /* skip rest */ + + if (IsA(node, RangeTblEntry)) { + RangeTblEntry *rte = (RangeTblEntry*)node; + + if (rte->rtekind == RTE_RELATION) + { + if (context->count == 0) + { + context->count++; + context->rte=rte; + } + else if (rte->relid != context->rte->relid) + { + context->count++; + return true; /* more that one relation in subtree */ + } + } + else if (!(rte->rtekind == RTE_SUBQUERY || rte->rtekind == RTE_JOIN || + rte->rtekind == RTE_CTE)) + { + context->count++; + return true; /* more that one relation in subtree */ + } + + return false; /* allow range_table_walker to continue */ + } + + if (IsA(node, Query)) + return query_tree_walker((Query *) node, find_rte_walker, + (void *) context, QTW_EXAMINE_RTES_BEFORE); + + return expression_tree_walker(node, find_rte_walker, (void *) context); +} + +static RangeTblEntry* +find_single_rte(RangeTblEntry *node) +{ + RangeTblEntryContext context; + + context.rte = NULL; + context.count = 0; + + (void)range_table_walker(list_make1(node), + find_rte_walker, + (void *) &context, QTW_EXAMINE_RTES_BEFORE); + + return context.count == 1 ? context.rte : NULL; +} + +#define IsSameRelationRTE(a, b) ( \ + (a)->rtekind == (b)->rtekind && \ + (a)->rtekind == RTE_RELATION && \ + (a)->relid == (b)->relid \ +) + + +/* + * Any self join or join with aggregation over the same table + */ + +static CorrelationKind +get_correlation_kind(PlannerInfo *root, int varRelid, OpExpr* expr) +{ + Node *left_arg, *right_arg; + Relids left_varnos, right_varnos; + int left_varno, right_varno; + RangeTblEntry *left_rte, *right_rte; + + if (varRelid != 0) + /* We consider only case of joins, not restriction mode */ + return CKIndepend; + + /* Check if it is equality comparison */ + if (get_oprrest(expr->opno) != F_EQSEL) + return CKIndepend; + + left_arg = linitial(expr->args); + right_arg = lsecond(expr->args); + + /* + * Check if it is join of two different relations + */ + left_varnos = pull_varnos(left_arg); + right_varnos = pull_varnos(right_arg); + if (!bms_get_singleton_member(left_varnos, &left_varno) || + !bms_get_singleton_member(right_varnos, &right_varno) || + left_varno == right_varno) + return CKIndepend; + + left_rte = planner_rt_fetch(left_varno, root); + right_rte = planner_rt_fetch(right_varno, root); + + if (IsSameRelationRTE(left_rte, right_rte)) + { + Var *lvar = get_var(left_arg), + *rvar = get_var(right_arg); + + /* self join detected, check if it simple a=b clause */ + if (lvar == NULL || rvar == NULL) + return CKLikelySelf; + return (lvar->varattno == rvar->varattno) ? + CKSelf : CKLikelySelf; + } + + if ((left_rte = find_single_rte(left_rte)) == NULL) + return CKIndepend; + if ((right_rte = find_single_rte(right_rte)) == NULL) + return CKIndepend; + + if (IsSameRelationRTE(left_rte, right_rte)) + { + /* self join detected, but over some transformation which cannot be + * flatten */ + return CKLikelySelf; + } + + return CKIndepend; +} /* * clause_selectivity - diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c index 4edc859cb57..9dfca34c156 100644 --- a/src/backend/optimizer/path/costsize.c +++ b/src/backend/optimizer/path/costsize.c @@ -148,6 +148,7 @@ typedef struct { PlannerInfo *root; QualCost total; + bool calccoalesce; } cost_qual_eval_context; static List *extract_nonindex_conditions(List *qual_clauses, List *indexclauses); @@ -182,7 +183,9 @@ static void set_rel_width(PlannerInfo *root, RelOptInfo *rel); static double relation_byte_size(double tuples, int width); static double page_size(double tuples, int width); static double get_parallel_divisor(Path *path); - +static Cost compute_cpu_sort_cost(PlannerInfo *root, List *pathkeys, int nPresortedKeys, + Cost comparison_cost, double tuples, double output_tuples, + bool heapSort); /* * clamp_row_est @@ -727,7 +730,7 @@ cost_index(IndexPath *path, PlannerInfo *root, double loop_count, cost_qual_eval(&qpqual_cost, qpquals, root); startup_cost += qpqual_cost.startup; - cpu_per_tuple = cpu_tuple_cost + qpqual_cost.per_tuple; + cpu_per_tuple = cpu_tuple_cost + 2.0*qpqual_cost.per_tuple; cpu_run_cost += cpu_per_tuple * tuples_fetched; @@ -1013,7 +1016,7 @@ cost_bitmap_heap_scan(Path *path, PlannerInfo *root, RelOptInfo *baserel, get_restriction_qual_cost(root, baserel, param_info, &qpqual_cost); startup_cost += qpqual_cost.startup; - cpu_per_tuple = cpu_tuple_cost + qpqual_cost.per_tuple; + cpu_per_tuple = cpu_tuple_cost + 2.0*qpqual_cost.per_tuple; cpu_run_cost = cpu_per_tuple * tuples_fetched; /* Adjust costing for parallelism, if used. */ @@ -1686,7 +1689,8 @@ cost_recursive_union(Path *runion, Path *nrterm, Path *rterm) * 'limit_tuples' is the bound on the number of output tuples; -1 if no bound */ static void -cost_tuplesort(Cost *startup_cost, Cost *run_cost, +cost_tuplesort(PlannerInfo *root, List *pathkeys, + Cost *startup_cost, Cost *run_cost, double tuples, int width, Cost comparison_cost, int sort_mem, double limit_tuples) @@ -1703,9 +1707,6 @@ cost_tuplesort(Cost *startup_cost, Cost *run_cost, if (tuples < 2.0) tuples = 2.0; - /* Include the default cost-per-comparison */ - comparison_cost += 2.0 * cpu_operator_cost; - /* Do we have a useful LIMIT? */ if (limit_tuples > 0 && limit_tuples < tuples) { @@ -1734,7 +1735,9 @@ cost_tuplesort(Cost *startup_cost, Cost *run_cost, * * Assume about N log2 N comparisons */ - *startup_cost = comparison_cost * tuples * LOG2(tuples); + *startup_cost = compute_cpu_sort_cost(root, pathkeys, 0, + comparison_cost, tuples, + tuples, false); /* Disk costs */ @@ -1756,12 +1759,16 @@ cost_tuplesort(Cost *startup_cost, Cost *run_cost, * factor is a bit higher than for quicksort. Tweak it so that the * cost curve is continuous at the crossover point. */ - *startup_cost = comparison_cost * tuples * LOG2(2.0 * output_tuples); + *startup_cost = compute_cpu_sort_cost(root, pathkeys, 0, + comparison_cost, tuples, + tuples, true); } else { /* We'll use plain quicksort on all the input tuples */ - *startup_cost = comparison_cost * tuples * LOG2(tuples); + *startup_cost = compute_cpu_sort_cost(root, pathkeys, 0, + comparison_cost, tuples, + tuples, false); } /* @@ -1879,7 +1886,7 @@ cost_incremental_sort(Path *path, * pessimistic about incremental sort performance and increase its average * group size by half. */ - cost_tuplesort(&group_startup_cost, &group_run_cost, + cost_tuplesort(root, pathkeys, &group_startup_cost, &group_run_cost, 1.5 * group_tuples, width, comparison_cost, sort_mem, limit_tuples); @@ -1914,6 +1921,287 @@ cost_incremental_sort(Path *path, path->total_cost = startup_cost + run_cost; } +/* + * is_fake_var + * Workaround for generate_append_tlist() which generates fake Vars with + * varno == 0, that will cause a fail of estimate_num_group() call + */ +static bool +is_fake_var(Expr *expr) +{ + if (IsA(expr, RelabelType)) + expr = (Expr *) ((RelabelType *) expr)->arg; + + return (IsA(expr, Var) && ((Var *) expr)->varno == 0); +} + +/* + * get_width_cost_multiplier + * Returns relative complexity of comparing two valyes based on it's width. + * The idea behind - long values have more expensive comparison. Return value is + * in cpu_operator_cost unit. + */ +static double +get_width_cost_multiplier(PlannerInfo *root, Expr *expr) +{ + double width = -1.0; /* fake value */ + + if (IsA(expr, RelabelType)) + expr = (Expr *) ((RelabelType *) expr)->arg; + + /* Try to find actual stat in corresonding relation */ + if (IsA(expr, Var)) + { + Var *var = (Var *) expr; + + if (var->varno > 0 && var->varno < root->simple_rel_array_size) + { + RelOptInfo *rel = root->simple_rel_array[var->varno]; + + if (rel != NULL && + var->varattno >= rel->min_attr && + var->varattno <= rel->max_attr) + { + int ndx = var->varattno - rel->min_attr; + + if (rel->attr_widths[ndx] > 0) + width = rel->attr_widths[ndx]; + } + } + } + + /* Didn't find any actual stats, use estimation by type */ + if (width < 0.0) + { + Node *node = (Node*) expr; + + width = get_typavgwidth(exprType(node), exprTypmod(node)); + } + + /* + * Any value in pgsql is passed by Datum type, so any operation with value + * could not be cheaper than operation with Datum type + */ + if (width <= sizeof(Datum)) + return 1.0; + + /* + * Seems, cost of comparision is not directly proportional to args width, + * because comparing args could be differ width (we known only average over + * column) and difference often could be defined only by looking on first + * bytes. So, use log16(width) as estimation. + */ + return 1.0 + 0.125 * LOG2(width / sizeof(Datum)); +} + +/* + * compute_cpu_sort_cost + * compute CPU cost of sort (i.e. in-memory) + * + * NOTE: some callers currently pass NIL for pathkeys because they + * can't conveniently supply the sort keys. In this case, it will fallback to + * simple comparison cost estimate. + * + * Estimation algorithm is based on ideas from course Algorithms, + * Robert Sedgewick, Kevin Wayne, https://algs4.cs.princeton.edu/home/ and paper + * "Quicksort Is Optimal For Many Equal Keys", Sebastian Wild, + * arXiv:1608.04906v4 [cs.DS] 1 Nov 2017. + * + * In term of that papers, let N - number of tuples, Xi - number of tuples with + * key Ki, then estimation is: + * log(N! / (X1! * X2! * ..)) ~ sum(Xi * log(N/Xi)) + * In our case all Xi are the same because noew we don't have an estimation of + * group sizes, we have only estimation of number of groups. In this case, + * formula becomes: N * log(NumberOfGroups). Next, to support correct estimation + * of multicolumn sort we need separately compute each column, so, let k is a + * column number, Gk - number of groups defined by k columns: + * N * sum( Fk * log(Gk) ) + * Fk is a function costs (includeing width) for k columns. + */ + +static Cost +compute_cpu_sort_cost(PlannerInfo *root, List *pathkeys, int nPresortedKeys, + Cost comparison_cost, double tuples, double output_tuples, + bool heapSort) +{ + Cost per_tuple_cost = 0.0; + ListCell *lc; + List *pathkeyExprs = NIL; + double tuplesPerPrevGroup = tuples; + double totalFuncCost = 1.0; + bool has_fake_var = false; + int i = 0; + Oid prev_datatype = InvalidOid; + Cost funcCost = 0.; + List *cache_varinfos = NIL; + + /* fallback if pathkeys is unknown */ + if (list_length(pathkeys) == 0) + { + /* + * If we'll use a bounded heap-sort keeping just K tuples in memory, for + * a total number of tuple comparisons of N log2 K; but the constant + * factor is a bit higher than for quicksort. Tweak it so that the + * cost curve is continuous at the crossover point. + */ + output_tuples = (heapSort) ? 2.0 * output_tuples : tuples; + per_tuple_cost += 2.0 * cpu_operator_cost * LOG2(output_tuples); + + /* add cost provided by caller */ + per_tuple_cost += comparison_cost; + + return per_tuple_cost * tuples; + } + + /* + * Computing total cost of sorting takes into account: + * - per column comparison function cost + * - we try to compute needed number of comparison per column + */ + + foreach(lc, pathkeys) + { + PathKey *pathkey = (PathKey*)lfirst(lc); + EquivalenceMember *em; + double nGroups, + correctedNGroups; + + /* + * We believe than equivalence members aren't very different, so, to + * estimate cost we take just first member + */ + em = (EquivalenceMember *) linitial(pathkey->pk_eclass->ec_members); + + if (em->em_datatype != InvalidOid) + { + /* do not lookup funcCost if data type is the same as previous */ + if (prev_datatype != em->em_datatype) + { + Oid sortop; + QualCost cost; + + sortop = get_opfamily_member(pathkey->pk_opfamily, + em->em_datatype, em->em_datatype, + pathkey->pk_strategy); + + cost.startup = 0; + cost.per_tuple = 0; + add_function_cost(root, get_opcode(sortop), NULL, &cost); + funcCost = cost.per_tuple / cpu_operator_cost; + prev_datatype = em->em_datatype; + } + } + else + funcCost = 1.0; /* fallback */ + + /* Try to take into account actual width fee */ + funcCost *= get_width_cost_multiplier(root, em->em_expr); + + totalFuncCost += funcCost; + + /* remeber if we have a fake var in pathkeys */ + has_fake_var |= is_fake_var(em->em_expr); + pathkeyExprs = lappend(pathkeyExprs, em->em_expr); + + /* + * Prevent call estimate_num_groups() with fake Var. Note, + * pathkeyExprs contains only previous columns + */ + if (has_fake_var == false) + /* + * Recursively compute number of group in group from previous step + */ + nGroups = estimate_num_groups_incremental(root, pathkeyExprs, + tuplesPerPrevGroup, NULL, + &cache_varinfos, + list_length(pathkeyExprs) - 1); + else if (tuples > 4.0) + /* + * Use geometric mean as estimation if there is no any stats. + * Don't use DEFAULT_NUM_DISTINCT because it used for only one + * column while here we try to estimate number of groups over + * set of columns. + */ + nGroups = ceil(2.0 + sqrt(tuples) * + list_length(pathkeyExprs) / list_length(pathkeys)); + else + nGroups = tuples; + + /* + * Presorted keys aren't participated in comparison but still checked + * by qsort comparator. + */ + if (i >= nPresortedKeys) + { + if (heapSort) + { + if (tuplesPerPrevGroup < output_tuples) + /* comparing only inside output_tuples */ + correctedNGroups = + ceil(2.0 * output_tuples / (tuplesPerPrevGroup / nGroups)); + else + /* two groups - in output and out */ + correctedNGroups = 2.0; + } + else + correctedNGroups = nGroups; + + if (correctedNGroups <= 1.0) + correctedNGroups = 2.0; + else + correctedNGroups = ceil(correctedNGroups); + per_tuple_cost += totalFuncCost * LOG2(correctedNGroups); + } + + i++; + + /* + * Real-world distribution isn't uniform but now we don't have a way to + * determine that, so, add multiplier to get closer to worst case. + */ + tuplesPerPrevGroup = ceil(1.5 * tuplesPerPrevGroup / nGroups); + + /* + * We could skip all followed columns for cost estimation, because we + * believe that tuples are unique by set ot previous columns + */ + if (tuplesPerPrevGroup <= 1.0) + break; + } + + list_free(pathkeyExprs); + + /* per_tuple_cost is in cpu_operator_cost units */ + per_tuple_cost *= cpu_operator_cost; + + /* + * Accordingly to "Introduction to algorithms", Thomas H. Cormen, Charles E. + * Leiserson, Ronald L. Rivest, ISBN 0-07-013143-0, quicksort estimation + * formula has additional term proportional to number of tuples (See Chapter + * 8.2 and Theorem 4.1). It has meaning with low number of tuples, + * approximately less that 1e4. Of course, it could be inmplemented as + * additional multiplier under logarithm, but use more complicated formula + * which takes into account number of unique tuples and it isn't clear how + * to combine multiplier with groups. Estimate it as 10 in cpu_operator_cost + * unit. + */ + per_tuple_cost += 10 * cpu_operator_cost; + + per_tuple_cost += comparison_cost; + + return tuples * per_tuple_cost; +} + +/* + * simple wrapper just to estimate best sort path + */ +Cost +cost_sort_estimate(PlannerInfo *root, List *pathkeys, int nPresortedKeys, + double tuples) +{ + return compute_cpu_sort_cost(root, pathkeys, nPresortedKeys, + 0, tuples, tuples, false); +} /* * cost_sort * Determines and returns the cost of sorting a relation, including @@ -1936,7 +2224,7 @@ cost_sort(Path *path, PlannerInfo *root, Cost startup_cost; Cost run_cost; - cost_tuplesort(&startup_cost, &run_cost, + cost_tuplesort(root, pathkeys, &startup_cost, &run_cost, tuples, width, comparison_cost, sort_mem, limit_tuples); @@ -2034,7 +2322,7 @@ append_nonpartial_cost(List *subpaths, int numpaths, int parallel_workers) * Determines and returns the cost of an Append node. */ void -cost_append(AppendPath *apath) +cost_append(AppendPath *apath, PlannerInfo *root) { ListCell *l; @@ -2102,7 +2390,7 @@ cost_append(AppendPath *apath) * any child. */ cost_sort(&sort_path, - NULL, /* doesn't currently need root */ + root, pathkeys, subpath->total_cost, subpath->rows, @@ -3126,8 +3414,9 @@ initial_cost_mergejoin(PlannerInfo *root, JoinCostWorkspace *workspace, /* CPU costs left for later */ /* Public result fields */ - workspace->startup_cost = startup_cost; - workspace->total_cost = startup_cost + run_cost + inner_run_cost; + workspace->startup_cost = startup_cost + outer_path->total_cost/outer_rows + + inner_path->total_cost/inner_rows; + workspace->total_cost = workspace->startup_cost + run_cost + inner_run_cost; /* Save private data for final_cost_mergejoin */ workspace->run_cost = run_cost; workspace->inner_run_cost = inner_run_cost; @@ -4063,6 +4352,7 @@ cost_qual_eval(QualCost *cost, List *quals, PlannerInfo *root) context.root = root; context.total.startup = 0; context.total.per_tuple = 0; + context.calccoalesce = true; /* We don't charge any cost for the implicit ANDing at top level ... */ @@ -4088,6 +4378,22 @@ cost_qual_eval_node(QualCost *cost, Node *qual, PlannerInfo *root) context.root = root; context.total.startup = 0; context.total.per_tuple = 0; + context.calccoalesce = true; + + cost_qual_eval_walker(qual, &context); + + *cost = context.total; +} + +void +cost_qual_eval_node_index(QualCost *cost, Node *qual, PlannerInfo *root) +{ + cost_qual_eval_context context; + + context.root = root; + context.total.startup = 0; + context.total.per_tuple = 0; + context.calccoalesce = false; cost_qual_eval_walker(qual, &context); @@ -4117,6 +4423,7 @@ cost_qual_eval_walker(Node *node, cost_qual_eval_context *context) locContext.root = context->root; locContext.total.startup = 0; locContext.total.per_tuple = 0; + locContext.calccoalesce = context->calccoalesce; /* * For an OR clause, recurse into the marked-up tree so that we @@ -4328,6 +4635,11 @@ cost_qual_eval_walker(Node *node, cost_qual_eval_context *context) */ return false; } + else if (IsA(node, CoalesceExpr) && context->calccoalesce) + { + context->total.per_tuple += cpu_operator_cost * + list_length(((CoalesceExpr *) node)->args); + } /* recurse into children */ return expression_tree_walker(node, cost_qual_eval_walker, @@ -4814,6 +5126,7 @@ calc_joinrel_size_estimate(PlannerInfo *root, Selectivity jselec; Selectivity pselec; double nrows; + bool apply_righthand = false; /* * Compute joinclause selectivity. Note that we are only considering @@ -4852,9 +5165,11 @@ calc_joinrel_size_estimate(PlannerInfo *root, { RestrictInfo *rinfo = lfirst_node(RestrictInfo, l); - if (RINFO_IS_PUSHED_DOWN(rinfo, joinrel->relids)) + if (RINFO_IS_PUSHED_DOWN(rinfo, joinrel->relids)) { pushedquals = lappend(pushedquals, rinfo); - else + apply_righthand |= bms_overlap(rinfo->clause_relids, + sjinfo->min_righthand); + } else joinquals = lappend(joinquals, rinfo); } @@ -4906,6 +5221,8 @@ calc_joinrel_size_estimate(PlannerInfo *root, nrows = outer_rows * inner_rows * fkselec * jselec; if (nrows < outer_rows) nrows = outer_rows; + if (apply_righthand && inner_rows < outer_rows) + pselec *= inner_rows / outer_rows; nrows *= pselec; break; case JOIN_FULL: diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c index 159bb7e6531..02841c8a92b 100644 --- a/src/backend/optimizer/path/equivclass.c +++ b/src/backend/optimizer/path/equivclass.c @@ -682,7 +682,18 @@ get_eclass_for_sort_expr(PlannerInfo *root, if (opcintype == cur_em->em_datatype && equal(expr, cur_em->em_expr)) - return cur_ec; /* Match! */ + { + /* + * Match! + * + * Copy sortref if it wasn't set yet, it's possible if ec was + * constructed from WHERE clause, ie it doesn't have target + * reference at all + */ + if (cur_ec->ec_sortref == 0 && sortref > 0) + cur_ec->ec_sortref = sortref; + return cur_ec; + } } } diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c index 1d899f80ae9..0ed379cff7e 100644 --- a/src/backend/optimizer/path/indxpath.c +++ b/src/backend/optimizer/path/indxpath.c @@ -35,7 +35,6 @@ #include "utils/lsyscache.h" #include "utils/selfuncs.h" - /* source-code-compatibility hacks for pull_varnos() API change */ #define pull_varnos(a,b) pull_varnos_new(a,b) #undef make_simple_restrictinfo @@ -117,8 +116,6 @@ static List *build_index_paths(PlannerInfo *root, RelOptInfo *rel, bool *skip_lower_saop); static List *build_paths_for_OR(PlannerInfo *root, RelOptInfo *rel, List *clauses, List *other_clauses); -static List *generate_bitmap_or_paths(PlannerInfo *root, RelOptInfo *rel, - List *clauses, List *other_clauses); static Path *choose_bitmap_and(PlannerInfo *root, RelOptInfo *rel, List *paths); static int path_usage_comparator(const void *a, const void *b); @@ -1257,7 +1254,7 @@ build_paths_for_OR(PlannerInfo *root, RelOptInfo *rel, * for the purpose of generating indexquals, but are not to be searched for * ORs. (See build_paths_for_OR() for motivation.) */ -static List * +List * generate_bitmap_or_paths(PlannerInfo *root, RelOptInfo *rel, List *clauses, List *other_clauses) { @@ -3295,7 +3292,6 @@ match_clause_to_ordering_op(IndexOptInfo *index, return clause; } - /**************************************************************************** * ---- ROUTINES TO DO PARTIAL INDEX PREDICATE TESTS ---- ****************************************************************************/ @@ -3494,7 +3490,8 @@ ec_member_matches_indexcol(PlannerInfo *root, RelOptInfo *rel, * relation_has_unique_index_for * Determine whether the relation provably has at most one row satisfying * a set of equality conditions, because the conditions constrain all - * columns of some unique index. + * columns of some unique index. If index_info is not null, it is set to + * point to a new UniqueIndexInfo containing the index and conditions. * * The conditions can be represented in either or both of two ways: * 1. A list of RestrictInfo nodes, where the caller has already determined @@ -3515,7 +3512,8 @@ ec_member_matches_indexcol(PlannerInfo *root, RelOptInfo *rel, bool relation_has_unique_index_for(PlannerInfo *root, RelOptInfo *rel, List *restrictlist, - List *exprlist, List *oprlist) + List *exprlist, List *oprlist, + UniqueIndexInfo **index_info) { ListCell *ic; @@ -3571,6 +3569,7 @@ relation_has_unique_index_for(PlannerInfo *root, RelOptInfo *rel, { IndexOptInfo *ind = (IndexOptInfo *) lfirst(ic); int c; + List *matched_restrictlist = NIL; /* * If the index is not unique, or not immediately enforced, or if it's @@ -3622,6 +3621,7 @@ relation_has_unique_index_for(PlannerInfo *root, RelOptInfo *rel, if (match_index_to_operand(rexpr, c, ind)) { matched = true; /* column is unique */ + matched_restrictlist = lappend(matched_restrictlist, rinfo); break; } } @@ -3664,7 +3664,25 @@ relation_has_unique_index_for(PlannerInfo *root, RelOptInfo *rel, /* Matched all key columns of this index? */ if (c == ind->nkeycolumns) + { + if (index_info != NULL) + { + /* This may be called in GEQO memory context. */ + MemoryContext oldContext = MemoryContextSwitchTo(root->planner_cxt); + *index_info = palloc(sizeof(UniqueIndexInfo)); + (*index_info)->index = ind; + (*index_info)->clauses = list_copy(matched_restrictlist); + MemoryContextSwitchTo(oldContext); + } + + if (matched_restrictlist) + list_free(matched_restrictlist); + return true; + } + + if (matched_restrictlist) + list_free(matched_restrictlist); } return false; diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c index 1e444e8addf..8bea6d7aa03 100644 --- a/src/backend/optimizer/path/joinpath.c +++ b/src/backend/optimizer/path/joinpath.c @@ -178,7 +178,8 @@ add_paths_to_joinrel(PlannerInfo *root, innerrel, JOIN_INNER, restrictlist, - false); + false, + NULL /*index_info*/); break; default: extra.inner_unique = innerrel_is_unique(root, @@ -187,7 +188,8 @@ add_paths_to_joinrel(PlannerInfo *root, innerrel, jointype, restrictlist, - false); + false, + NULL /*index_info*/); break; } diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index 77585609ae6..2c565763c02 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -1279,7 +1279,7 @@ mark_dummy_rel(RelOptInfo *rel) /* Set up the dummy path */ add_path(rel, (Path *) create_append_path(NULL, rel, NIL, NIL, NIL, rel->lateral_relids, - 0, false, NIL, -1)); + 0, false, NIL, -1, false)); /* Set or update cheapest_total_path and related fields */ set_cheapest(rel); diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c index 4ce77ca5eed..4cea8b8e998 100644 --- a/src/backend/optimizer/path/pathkeys.c +++ b/src/backend/optimizer/path/pathkeys.c @@ -17,17 +17,20 @@ */ #include "postgres.h" +#include "miscadmin.h" #include "access/stratnum.h" #include "catalog/pg_opfamily.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" #include "nodes/plannodes.h" +#include "optimizer/clauses.h" +#include "optimizer/cost.h" #include "optimizer/optimizer.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "partitioning/partbounds.h" #include "utils/lsyscache.h" - +#include "utils/selfuncs.h" static bool pathkey_is_redundant(PathKey *new_pathkey, List *pathkeys); static bool matches_boolean_partition_clause(RestrictInfo *rinfo, @@ -388,6 +391,296 @@ pathkeys_count_contained_in(List *keys1, List *keys2, int *n_common) return (key1 == NULL); } +/* + * Reorder GROUP BY pathkeys and clauses to match order of pathkeys. Function + * returns new lists, original GROUP BY lists stay untouched. + */ +int +group_keys_reorder_by_pathkeys(List *pathkeys, List **group_pathkeys, + List **group_clauses) +{ + List *new_group_pathkeys= NIL, + *new_group_clauses = NIL; + ListCell *key; + int n; + + if (pathkeys == NIL || *group_pathkeys == NIL) + return 0; + + /* + * For each pathkey it tries to find corresponding GROUP BY pathkey and + * clause. + */ + foreach(key, pathkeys) + { + PathKey *pathkey = (PathKey *) lfirst(key); + SortGroupClause *sgc; + + /* + * Pathkey should use the same allocated struct, so, equiality of + * pointers is enough + */ + if (!list_member_ptr(*group_pathkeys, pathkey)) + break; + + new_group_pathkeys = lappend(new_group_pathkeys, pathkey); + + sgc = get_sortgroupref_clause(pathkey->pk_eclass->ec_sortref, + *group_clauses); + new_group_clauses = lappend(new_group_clauses, sgc); + } + + n = list_length(new_group_pathkeys); + + /* + * Just append the rest of pathkeys and clauses + */ + *group_pathkeys = list_concat_unique_ptr(new_group_pathkeys, + *group_pathkeys); + *group_clauses = list_concat_unique_ptr(new_group_clauses, + *group_clauses); + + return n; +} + +typedef struct MutatorState { + List *elemsList; + ListCell **elemCells; + void **elems; + int *positions; + int mutatorNColumns; + int count; +} MutatorState; + +static void +initMutator(MutatorState *state, List *elems, int start, int end) +{ + int i; + int n = end - start; + ListCell *lc; + + memset(state, 0, sizeof(*state)); + + state->mutatorNColumns = n; + + state->elemsList = list_copy(elems); + + state->elems = palloc(sizeof(void*) * n); + state->elemCells = palloc(sizeof(ListCell*) * n); + state->positions = palloc(sizeof(int) * n); + + i = 0; + for_each_cell(lc, state->elemsList, list_nth_cell(state->elemsList, start)) + { + state->elemCells[i] = lc; + state->elems[i] = lfirst(lc); + state->positions[i] = i + 1; + i++; + if (i >= n) + break; + } +} + +static void +swap(int *a, int i, int j) +{ + int s = a[i]; + + a[i] = a[j]; + a[j] = s; +} + +static bool +getNextSet(int *a, int n) +{ + int j, k, l, r; + + j = n - 2; + while (j >= 0 && a[j] >= a[j + 1]) + j--; + if (j < 0) + return false; + + k = n - 1; + while (k >= 0 && a[j] >= a[k]) + k--; + swap(a, j, k); + + l = j + 1; + r = n - 1; + while (l < r) + swap(a, l++, r--); + + + return true; +} + +static List* +doMutator(MutatorState *state) +{ + int i; + + state->count++; + + /* first set is original set */ + if (state->count == 1) + return state->elemsList; + + if (getNextSet(state->positions, state->mutatorNColumns) == false) + { + pfree(state->elems); + pfree(state->elemCells); + pfree(state->positions); + list_free(state->elemsList); + + return NIL; + } + + for(i=0; i<state->mutatorNColumns; i++) + lfirst(state->elemCells[i]) = + (void*) state->elems[ state->positions[i] - 1 ]; + + return state->elemsList; +} + +typedef struct { + Cost cost; + PathKey *pathkey; +} PathkeySortCost; + +static int +pathkey_sort_cost_comparator(const void *_a, const void *_b) +{ + const PathkeySortCost *a = (PathkeySortCost *) _a; + const PathkeySortCost *b = (PathkeySortCost *) _b; + + if (a->cost < b->cost) + return -1; + else if (a->cost == b->cost) + return 0; + return 1; +} +/* + * Order tail of list of group pathkeys by uniqueness descendetly. It allows to + * speedup sorting. Returns newly allocated lists, old ones stay untouched. + * n_preordered defines a head of list which order should be prevented. + */ +void +get_cheapest_group_keys_order(PlannerInfo *root, double nrows, + List **group_pathkeys, List **group_clauses, + int n_preordered) +{ + List *new_group_pathkeys = NIL, + *new_group_clauses = NIL, + *var_group_pathkeys; + + ListCell *cell; + MutatorState mstate; + double cheapest_sort_cost = -1.0; + + int nFreeKeys; + int nToPermute; + int i; + + if (list_length(*group_pathkeys) - n_preordered < 2) + return; /* nothing to do */ + + /* + * Will try to match ORDER BY pathkeys in hope that one sort is cheaper than + * two + */ + if (n_preordered == 0 && root->sort_pathkeys) + { + n_preordered = group_keys_reorder_by_pathkeys(root->sort_pathkeys, + group_pathkeys, + group_clauses); + + if (list_length(*group_pathkeys) - n_preordered < 2) + return; /* nothing to do */ + } + + /* + * Try all permutations of at most 4 cheapeast pathkeys. + */ + nFreeKeys = list_length(*group_pathkeys) - n_preordered; + + nToPermute = 4; + + if (list_length(*group_pathkeys) > 64 || nrows <= 1024.0 || + n_preordered > nToPermute) + return; + + if (nFreeKeys > nToPermute) + { + /* + * Sort the remaining pathkeys cheapest first. + */ + PathkeySortCost *costs = palloc(sizeof(*costs) * nFreeKeys); + cell = list_nth_cell(*group_pathkeys, n_preordered); + for (i = 0; cell != NULL; i++, (cell = lnext(*group_pathkeys, cell))) + { + List *to_cost = list_make1(lfirst(cell)); + + Assert(i < nFreeKeys); + + costs[i].pathkey = lfirst(cell); + costs[i].cost = cost_sort_estimate(root, to_cost, 0, nrows); + + pfree(to_cost); + } + qsort(costs, nFreeKeys, sizeof(*costs), pathkey_sort_cost_comparator); + + /* Construct the sorted list. First, the preordered pathkeys. */ + new_group_pathkeys = list_truncate(list_copy(*group_pathkeys), n_preordered); + + /* The rest, ordered by increasing cost */ + for (i = 0; i < nFreeKeys; i++) + new_group_pathkeys = lappend(new_group_pathkeys, costs[i].pathkey); + + pfree(costs); + } + else + { + new_group_pathkeys = list_copy(*group_pathkeys); + nToPermute = nFreeKeys; + } + + initMutator(&mstate, new_group_pathkeys, n_preordered, n_preordered + nToPermute); + + while((var_group_pathkeys = doMutator(&mstate)) != NIL) + { + Cost cost; + + cost = cost_sort_estimate(root, var_group_pathkeys, n_preordered, nrows); + + if (cost < cheapest_sort_cost || cheapest_sort_cost < 0) + { + list_free(new_group_pathkeys); + new_group_pathkeys = list_copy(var_group_pathkeys); + cheapest_sort_cost = cost; + } + } + + /* + * repeat order of pathkeys for clauses + */ + foreach(cell, new_group_pathkeys) + { + PathKey *pathkey = (PathKey *) lfirst(cell); + + new_group_clauses = lappend(new_group_clauses, + get_sortgroupref_clause(pathkey->pk_eclass->ec_sortref, + *group_clauses)); + } + + /* Just append the rest GROUP BY clauses */ + new_group_clauses = list_concat_unique_ptr(new_group_clauses, + *group_clauses); + + *group_pathkeys = new_group_pathkeys; + *group_clauses = new_group_clauses; +} + /* * get_cheapest_path_for_pathkeys * Find the cheapest path (according to the specified criterion) that @@ -1845,7 +2138,7 @@ right_merge_direction(PlannerInfo *root, PathKey *pathkey) * ordering. Thus we return 0, if no valuable keys are found, or the number * of leading keys shared by the list and the requested ordering.. */ -static int +int pathkeys_useful_for_ordering(PlannerInfo *root, List *pathkeys) { int n_common_pathkeys; @@ -1862,6 +2155,39 @@ pathkeys_useful_for_ordering(PlannerInfo *root, List *pathkeys) return n_common_pathkeys; } +/* + * pathkeys_useful_for_grouping + * Count the number of pathkeys that are useful for grouping (instead of + * explicit sort) + * + * Group pathkeys could be reordered, so we don't bother about actual order in + * pathkeys + */ +static int +pathkeys_useful_for_grouping(PlannerInfo *root, List *pathkeys) +{ + ListCell *key; + int n = 0; + + if (root->group_pathkeys == NIL) + return 0; /* no special ordering requested */ + + if (pathkeys == NIL) + return 0; /* unordered path */ + + foreach(key, pathkeys) + { + PathKey *pathkey = (PathKey *) lfirst(key); + + if (!list_member_ptr(root->group_pathkeys, pathkey)) + break; + + n++; + } + + return n; +} + /* * truncate_useless_pathkeys * Shorten the given pathkey list to just the useful pathkeys. @@ -1876,6 +2202,9 @@ truncate_useless_pathkeys(PlannerInfo *root, nuseful = pathkeys_useful_for_merging(root, rel, pathkeys); nuseful2 = pathkeys_useful_for_ordering(root, pathkeys); + if (nuseful2 > nuseful) + nuseful = nuseful2; + nuseful2 = pathkeys_useful_for_grouping(root, pathkeys); if (nuseful2 > nuseful) nuseful = nuseful2; @@ -1911,6 +2240,8 @@ has_useful_pathkeys(PlannerInfo *root, RelOptInfo *rel) { if (rel->joininfo != NIL || rel->has_eclass_joins) return true; /* might be able to use pathkeys for merging */ + if (root->group_pathkeys != NIL) + return true; /* might be able to use pathkeys for grouping */ if (root->query_pathkeys != NIL) return true; /* might be able to use them for ordering */ return false; /* definitely useless */ diff --git a/src/backend/optimizer/plan/analyzejoins.c b/src/backend/optimizer/plan/analyzejoins.c index 4d42625e1d4..2c8e6cf21b2 100644 --- a/src/backend/optimizer/plan/analyzejoins.c +++ b/src/backend/optimizer/plan/analyzejoins.c @@ -22,6 +22,7 @@ */ #include "postgres.h" +#include "catalog/pg_class.h" #include "nodes/nodeFuncs.h" #include "optimizer/clauses.h" #include "optimizer/joininfo.h" @@ -29,8 +30,10 @@ #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/planmain.h" +#include "optimizer/restrictinfo.h" #include "optimizer/tlist.h" #include "utils/lsyscache.h" +#include "utils/memutils.h" /* source-code-compatibility hacks for pull_varnos() API change */ #define pull_varnos(a,b) pull_varnos_new(a,b) @@ -42,15 +45,17 @@ static void remove_rel_from_query(PlannerInfo *root, int relid, static List *remove_rel_from_joinlist(List *joinlist, int relid, int *nremoved); static bool rel_supports_distinctness(PlannerInfo *root, RelOptInfo *rel); static bool rel_is_distinct_for(PlannerInfo *root, RelOptInfo *rel, - List *clause_list); + List *clause_list, UniqueIndexInfo **info); static Oid distinct_col_search(int colno, List *colnos, List *opids); static bool is_innerrel_unique_for(PlannerInfo *root, Relids joinrelids, Relids outerrelids, RelOptInfo *innerrel, JoinType jointype, - List *restrictlist); + List *restrictlist, + UniqueIndexInfo **info); +static void change_rinfo(RestrictInfo* rinfo, Index from, Index to); /* * remove_useless_joins @@ -61,7 +66,7 @@ static bool is_innerrel_unique_for(PlannerInfo *root, * data structures that have to be updated are accessible via "root". */ List * -remove_useless_joins(PlannerInfo *root, List *joinlist) +remove_useless_left_joins(PlannerInfo *root, List *joinlist) { ListCell *lc; @@ -164,7 +169,6 @@ join_is_removable(PlannerInfo *root, SpecialJoinInfo *sjinfo) int innerrelid; RelOptInfo *innerrel; Relids joinrelids; - List *clause_list = NIL; ListCell *l; int attroff; @@ -240,67 +244,24 @@ join_is_removable(PlannerInfo *root, SpecialJoinInfo *sjinfo) } /* - * Search for mergejoinable clauses that constrain the inner rel against - * either the outer rel or a pseudoconstant. If an operator is - * mergejoinable then it behaves like equality for some btree opclass, so - * it's what we want. The mergejoinability test also eliminates clauses - * containing volatile functions, which we couldn't depend on. + * Check for pushed-down clauses referencing the inner rel. If there is + * such a clause then join removal has to be disallowed. We have to + * check this despite the previous attr_needed checks because of the + * possibility of pushed-down clauses referencing the rel. */ foreach(l, innerrel->joininfo) { RestrictInfo *restrictinfo = (RestrictInfo *) lfirst(l); - - /* - * If it's not a join clause for this outer join, we can't use it. - * Note that if the clause is pushed-down, then it is logically from - * above the outer join, even if it references no other rels (it might - * be from WHERE, for example). - */ - if (RINFO_IS_PUSHED_DOWN(restrictinfo, joinrelids)) - { - /* - * If such a clause actually references the inner rel then join - * removal has to be disallowed. We have to check this despite - * the previous attr_needed checks because of the possibility of - * pushed-down clauses referencing the rel. - */ - if (bms_is_member(innerrelid, restrictinfo->clause_relids)) + if (RINFO_IS_PUSHED_DOWN(restrictinfo, joinrelids) + && bms_is_member(innerrel->relid, restrictinfo->clause_relids)) return false; - continue; /* else, ignore; not useful here */ - } - - /* Ignore if it's not a mergejoinable clause */ - if (!restrictinfo->can_join || - restrictinfo->mergeopfamilies == NIL) - continue; /* not mergejoinable */ - - /* - * Check if clause has the form "outer op inner" or "inner op outer", - * and if so mark which side is inner. - */ - if (!clause_sides_match_join(restrictinfo, sjinfo->min_lefthand, - innerrel->relids)) - continue; /* no good for these input relations */ - - /* OK, add to list */ - clause_list = lappend(clause_list, restrictinfo); } - /* - * Now that we have the relevant equality join clauses, try to prove the - * innerrel distinct. - */ - if (rel_is_distinct_for(root, innerrel, clause_list)) - return true; - - /* - * Some day it would be nice to check for other methods of establishing - * distinctness. - */ - return false; + return is_innerrel_unique_for(root, joinrelids, sjinfo->min_lefthand, + innerrel, sjinfo->jointype, innerrel->joininfo, + NULL /*unique_index*/); } - /* * Remove the target relid from the planner's data structures, having * determined that there is no need to include it in the query. @@ -564,7 +525,7 @@ reduce_unique_semijoins(PlannerInfo *root) /* Test whether the innerrel is unique for those clauses. */ if (!innerrel_is_unique(root, joinrelids, sjinfo->min_lefthand, innerrel, - JOIN_SEMI, restrictlist, true)) + JOIN_SEMI, restrictlist, true, NULL /*index_info*/)) continue; /* OK, remove the SpecialJoinInfo from the list. */ @@ -638,9 +599,13 @@ rel_supports_distinctness(PlannerInfo *root, RelOptInfo *rel) * Note that the passed-in clause_list may be destructively modified! This * is OK for current uses, because the clause_list is built by the caller for * the sole purpose of passing to this function. + * + * If unique_index is not null, it is set to point to the index that guarantees + * uniqueness for a base relation. */ static bool -rel_is_distinct_for(PlannerInfo *root, RelOptInfo *rel, List *clause_list) +rel_is_distinct_for(PlannerInfo *root, RelOptInfo *rel, List *clause_list, + UniqueIndexInfo **index_info) { /* * We could skip a couple of tests here if we assume all callers checked @@ -656,8 +621,8 @@ rel_is_distinct_for(PlannerInfo *root, RelOptInfo *rel, List *clause_list) * relation_has_unique_index_for automatically adds any usable * restriction clauses for the rel, so we needn't do that here. */ - if (relation_has_unique_index_for(root, rel, clause_list, NIL, NIL)) - return true; + return relation_has_unique_index_for(root, rel, clause_list, NIL, NIL, + index_info); } else if (rel->rtekind == RTE_SUBQUERY) { @@ -961,6 +926,10 @@ distinct_col_search(int colno, List *colnos, List *opids) * heuristic about whether to cache negative answers; it should be "true" * if making an inquiry that is not part of the normal bottom-up join search * sequence. + * + * If index_info_out is not null, it is set to point to a new UniqueIndexInfo + * allocated in root memory context, that describes the index that guarantees + * uniqueness. */ bool innerrel_is_unique(PlannerInfo *root, @@ -969,12 +938,23 @@ innerrel_is_unique(PlannerInfo *root, RelOptInfo *innerrel, JoinType jointype, List *restrictlist, - bool force_cache) + bool force_cache, + UniqueIndexInfo **index_info_out) { MemoryContext old_context; ListCell *lc; + UniqueIndexInfo *index_info; - /* Certainly can't prove uniqueness when there are no joinclauses */ + if (index_info_out) + *index_info_out = NULL; + + /* + * It is possible to prove uniqueness even in the absence of joinclauses, + * just from baserestrictinfos alone. However, in these cases the inner + * relation returns one row at most, so join removal won't give much + * benefit. It seems better to save some planning time by ignoring these + * cases. + */ if (restrictlist == NIL) return false; @@ -994,10 +974,14 @@ innerrel_is_unique(PlannerInfo *root, */ foreach(lc, innerrel->unique_for_rels) { - Relids unique_for_rels = (Relids) lfirst(lc); + Relids unique_for_rels = (Relids) linitial(lfirst(lc)); if (bms_is_subset(unique_for_rels, outerrelids)) + { + if (index_info_out) + *index_info_out = lsecond(lfirst(lc)); return true; /* Success! */ + } } /* @@ -1014,7 +998,7 @@ innerrel_is_unique(PlannerInfo *root, /* No cached information, so try to make the proof. */ if (is_innerrel_unique_for(root, joinrelids, outerrelids, innerrel, - jointype, restrictlist)) + jointype, restrictlist, &index_info)) { /* * Cache the positive result for future probes, being sure to keep it @@ -1028,9 +1012,12 @@ innerrel_is_unique(PlannerInfo *root, */ old_context = MemoryContextSwitchTo(root->planner_cxt); innerrel->unique_for_rels = lappend(innerrel->unique_for_rels, - bms_copy(outerrelids)); + list_make2(bms_copy(outerrelids), index_info)); MemoryContextSwitchTo(old_context); + if (index_info_out) + *index_info_out = index_info; + return true; /* Success! */ } else @@ -1076,7 +1063,8 @@ is_innerrel_unique_for(PlannerInfo *root, Relids outerrelids, RelOptInfo *innerrel, JoinType jointype, - List *restrictlist) + List *restrictlist, + UniqueIndexInfo **index_info) { List *clause_list = NIL; ListCell *lc; @@ -1118,5 +1106,1056 @@ is_innerrel_unique_for(PlannerInfo *root, } /* Let rel_is_distinct_for() do the hard work */ - return rel_is_distinct_for(root, innerrel, clause_list); + return rel_is_distinct_for(root, innerrel, clause_list, index_info); +} + +typedef struct +{ + Index oldRelid; + Index newRelid; +} ChangeVarnoContext; + +static bool +change_varno_walker(Node *node, ChangeVarnoContext *context) +{ + if (node == NULL) + return false; + + if (IsA(node, Var) && ((Var *) node)->varno == context->oldRelid) + { + ((Var *) node)->varno = context->newRelid; + ((Var *) node)->varnosyn = context->newRelid; + return false; + } + if (IsA(node, RestrictInfo)) + { + change_rinfo((RestrictInfo*)node, context->oldRelid, context->newRelid); + return false; + } + + return expression_tree_walker(node, change_varno_walker, context); +} + +/* + * For all Vars in the expression that have varno = oldRelid, set + * varno = newRelid. + */ +static void +change_varno(Expr *expr, Index oldRelid, Index newRelid) +{ + ChangeVarnoContext context; + context.oldRelid = oldRelid; + context.newRelid = newRelid; + change_varno_walker((Node *) expr, &context); +} + +/* + * Substitute newId for oldId in relids. + */ +static void +change_relid(Relids *relids, Index oldId, Index newId) +{ + if (bms_is_member(oldId, *relids)) + *relids = bms_add_member(bms_del_member(bms_copy(*relids), oldId), newId); +} + +/* + * Substitute newId for oldId in field of RestrictInfo + */ +static void +change_rinfo(RestrictInfo* rinfo, Index from, Index to) +{ + bool is_req_equal = + (rinfo->required_relids == rinfo->clause_relids) ? true : false; + + /* + * At the required_relids initialization stage we do not create independent + * bms structure. The field is a link to clause_relids. During the call + * of change_relid() routine clause_relids can change memory location. + */ + change_relid(&rinfo->clause_relids, from, to); + if (is_req_equal) + rinfo->required_relids = rinfo->clause_relids; + else + change_relid(&rinfo->required_relids, from, to); + + change_varno(rinfo->clause, from, to); + change_varno(rinfo->orclause, from, to); + change_relid(&rinfo->left_relids, from, to); + change_relid(&rinfo->right_relids, from, to); + change_relid(&rinfo->outer_relids, from, to); + change_relid(&rinfo->nullable_relids, from, to); +} + +/* + * Update EC members to point to the remaining relation instead of the removed + * one, removing duplicates. + */ +static void +update_ec_members(EquivalenceClass *ec, Index toRemove, Index toKeep) +{ + ListCell *cell = NULL; + ListCell *next; + +restart: + next = list_head(ec->ec_members); + + while (next) + { + EquivalenceMember *em; + ListCell *otherCell; + + cell = next; + next = lnext(ec->ec_members, next); + + em = lfirst(cell); + + if (!bms_is_member(toRemove, em->em_relids)) + continue; + + change_relid(&em->em_relids, toRemove, toKeep); + /* We only process inner joins */ +// Assert(bms_is_empty(em->em_nullable_relids)); + change_varno(em->em_expr, toRemove, toKeep); + + /* + * After we switched the equivalence member to the remaining relation, + * check that it is not the same as the existing member, and if it + * is, delete it. + */ + foreach (otherCell, ec->ec_members) + { + EquivalenceMember *other; + + if (otherCell == cell) + continue; + + other = castNode(EquivalenceMember, lfirst(otherCell)); + if (equal(other->em_expr, em->em_expr)) + { + ec->ec_members = list_delete_cell(ec->ec_members, cell); + goto restart; + } + } + } +} + +/* + * Update EC sources to point to the remaining relation instead of the + * removed one. + */ +static void +update_ec_sources(List **sources, Index toRemove, Index toKeep) +{ + ListCell *cell = NULL; + ListCell *next; + +restart: + next = list_head(*sources); + + while (next) + { + RestrictInfo *rinfo; + ListCell *otherCell; + + cell = next; + next = lnext(*sources, next); + + rinfo = castNode(RestrictInfo, lfirst(cell)); + + if (!bms_is_member(toRemove, rinfo->required_relids)) + continue; + + change_varno(rinfo->clause, toRemove, toKeep); + + /* + * After switching the clause to the remaining relation, check it + * for redundancy with existing ones. We don't have to check for + * redundancy with derived clauses, because we've just deleted them. + */ + foreach (otherCell, *sources) + { + RestrictInfo *other; + + if (otherCell == cell) + continue; + + other = castNode(RestrictInfo, lfirst(otherCell)); + + if (equal(rinfo->clause, other->clause)) + { + *sources = list_delete_cell(*sources, cell); + cell = NULL; + break; + } + } + + if (otherCell == NULL) + { + /* We will keep this RestrictInfo, correct its relids. */ + change_relid(&rinfo->required_relids, toRemove, toKeep); + change_relid(&rinfo->left_relids, toRemove, toKeep); + change_relid(&rinfo->right_relids, toRemove, toKeep); + change_relid(&rinfo->clause_relids, toRemove, toKeep); + } + + if (cell == NULL) + goto restart; + } +} + +/* + * Scratch space for the unique self join removal code. + */ +typedef struct +{ + PlannerInfo *root; + + /* Temporary array for relation ids. */ + Index *relids; + + /* + * Array of Relids, one for each relation, indexed by relation id. + * Each element is a set of relation ids with which this relation + * has a special join. + */ + Relids *special_join_rels; + + /* Array of row marks indexed by relid. */ + PlanRowMark **row_marks; + + /* Bitmapset for join relids that is used to avoid reallocation. */ + Relids joinrelids; + + /* + * Top-level targetlist of the query. We have to update any references + * it has to the relations we remove. + */ + List *targetlist; +} UsjScratch; + + +/* + * Remove a relation after we have proven that it participates only in an + * unneeded unique self join. + * + * The joinclauses list is destructively changed. + */ +static void +remove_self_join_rel(UsjScratch *scratch, Relids joinrelids, List *joinclauses, + RelOptInfo *toKeep, RelOptInfo *toRemove) +{ + PlannerInfo *root = scratch->root; + ListCell *cell; + int i; + + /* Merge EC indexes */ + toKeep->eclass_indexes = bms_add_members(toRemove->eclass_indexes, + toKeep->eclass_indexes); + + /* + * Transfer join and restriction clauses from the removed relation to the + * remaining one. We change the Vars of the clause to point to the remaining + * relation instead of the removed one. The clauses that require a subset of + * joinrelids become restriction clauses of the remaining relation, and + * others remain join clauses. We append them to baserestrictinfo and + * joininfo respectively, trying not to introduce duplicates. + * + * We also have to process the 'joinclauses' list here, because it contains + * EC-derived join clauses which must become filter clauses. It is not enough + * to just correct the ECs, because the EC-derived restrictions are generated + * before join removal (see generate_base_implied_equalities). + */ + /* Replace removed relation in joininfo */ + foreach(cell, toRemove->joininfo) + { + RestrictInfo *rinfo = lfirst_node(RestrictInfo, cell); + + change_rinfo(rinfo, toRemove->relid, toKeep->relid); + + /* + * If this clause is a mergejoinable equality clause that compares a + * variable to itself, i.e., has the form of "X=X", replace it with + * null test. + */ + if (rinfo->mergeopfamilies && IsA(rinfo->clause, OpExpr)) + { + Expr *leftOp, *rightOp; + + leftOp = (Expr *) get_leftop(rinfo->clause); + rightOp = (Expr *) get_rightop(rinfo->clause); + + if (leftOp != NULL && equal(leftOp, rightOp)) + { + NullTest *nullTest = makeNode(NullTest); + nullTest->arg = leftOp; + nullTest->nulltesttype = IS_NOT_NULL; + nullTest->argisrow = false; + nullTest->location = -1; + rinfo->clause = (Expr*)nullTest; + } + } + } + + foreach(cell, joinclauses) + { + RestrictInfo *rinfo = lfirst_node(RestrictInfo, cell); + + change_rinfo(rinfo, toRemove->relid, toKeep->relid); + + /* + * If this clause is a mergejoinable equality clause that compares a + * variable to itself, i.e., has the form of "X=X", replace it with + * null test. + */ + if (rinfo->mergeopfamilies && IsA(rinfo->clause, OpExpr)) + { + Expr *leftOp, *rightOp; + + leftOp = (Expr *) get_leftop(rinfo->clause); + rightOp = (Expr *) get_rightop(rinfo->clause); + + if (leftOp != NULL && equal(leftOp, rightOp)) + { + NullTest *nullTest = makeNode(NullTest); + nullTest->arg = leftOp; + nullTest->nulltesttype = IS_NOT_NULL; + nullTest->argisrow = false; + nullTest->location = -1; + rinfo->clause = (Expr*)nullTest; + toKeep->baserestrictinfo = lappend(toKeep->baserestrictinfo, rinfo); + //toKeep->joininfo = lappend(toKeep->joininfo, rinfo); + } + } + } + + + /* Tranfer removed relations clauses to kept relation */ + foreach(cell, toRemove->baserestrictinfo) + { + ListCell *otherCell; + RestrictInfo *rinfo = lfirst_node(RestrictInfo, cell); + List **target = &toKeep->baserestrictinfo; + + /* + * Replace the references to the removed relation with references to + * the remaining one. We won't necessarily add this clause, because + * it may be already present in the joininfo or baserestrictinfo. + * Still, we have to switch it to point to the remaining relation. + * This is important for join clauses that reference both relations, + * because they are included in both joininfos. + */ + change_rinfo(rinfo, toRemove->relid, toKeep->relid); + + /* + * Don't add the clause if it is already present in the list, or + * derived from the same equivalence class, or is the same as another + * clause. + */ + foreach (otherCell, *target) + { + RestrictInfo *other = lfirst(otherCell); + if (other == rinfo + || (rinfo->parent_ec != NULL + && other->parent_ec == rinfo->parent_ec) + || equal(rinfo->clause, other->clause)) + { + break; + } + } + + if (otherCell != NULL) + continue; + + *target = lappend(*target, rinfo); + } + + /* Tranfer removed relations clauses to kept relation */ + foreach(cell, toRemove->joininfo) + { + ListCell *otherCell; + RestrictInfo *rinfo = lfirst_node(RestrictInfo, cell); + List **target = &toKeep->joininfo; + + /* + * Replace the references to the removed relation with references to + * the remaining one. We won't necessarily add this clause, because + * it may be already present in the joininfo or baserestrictinfo. + * Still, we have to switch it to point to the remaining relation. + * This is important for join clauses that reference both relations, + * because they are included in both joininfos. + */ + change_varno(rinfo->clause, toRemove->relid, toKeep->relid); + change_relid(&rinfo->required_relids, toRemove->relid, toKeep->relid); + change_relid(&rinfo->left_relids, toRemove->relid, toKeep->relid); + change_relid(&rinfo->right_relids, toRemove->relid, toKeep->relid); + change_relid(&rinfo->clause_relids, toRemove->relid, toKeep->relid); + + /* + * Don't add the clause if it is already present in the list, or + * derived from the same equivalence class, or is the same as another + * clause. + */ + foreach (otherCell, *target) + { + RestrictInfo *other = lfirst(otherCell); + if (other == rinfo + || (rinfo->parent_ec != NULL + && other->parent_ec == rinfo->parent_ec) + || equal(rinfo->clause, other->clause)) + { + break; + } + } + + if (otherCell != NULL) + continue; + + /* + * If this clause is a mergejoinable equality clause that compares a + * variable to itself, i.e., has the form of "X=X", replace it with + * null test. + */ + if (rinfo->mergeopfamilies && IsA(rinfo->clause, OpExpr)) + { + Expr *leftOp, *rightOp; + + Assert(IsA(rinfo->clause, OpExpr)); + + leftOp = (Expr *) get_leftop(rinfo->clause); + rightOp = (Expr *) get_rightop(rinfo->clause); + + if (leftOp != NULL && equal(leftOp, rightOp)) + { + NullTest *nullTest = makeNode(NullTest); + nullTest->arg = leftOp; + nullTest->nulltesttype = IS_NOT_NULL; + nullTest->argisrow = false; + nullTest->location = -1; + rinfo->clause = (Expr*)nullTest; + } + } + + *target = lappend(*target, rinfo); + } + + /* + * Transfer the targetlist and attr_needed flags. + */ + Assert(toRemove->reltarget->sortgrouprefs == 0); + + foreach (cell, toRemove->reltarget->exprs) + { + Expr *node = lfirst(cell); + change_varno(node, toRemove->relid, toKeep->relid); + if (!list_member(toKeep->reltarget->exprs, node)) + toKeep->reltarget->exprs = lappend(toKeep->reltarget->exprs, + node); + } + + for (i = toKeep->min_attr; i <= toKeep->max_attr; i++) + { + int attno = i - toKeep->min_attr; + toKeep->attr_needed[attno] = bms_add_members(toKeep->attr_needed[attno], + toRemove->attr_needed[attno]); + } + + /* + * If the removed relation has a row mark, transfer it to the remaining one. + * + * If both rels have row marks, just keep the one corresponding to the + * remaining relation, because we verified earlier that they have the same + * strength. + * + * Also make sure that the scratch->row_marks cache is up to date, because + * we are going to use it for further join removals. + */ + if (scratch->row_marks[toRemove->relid]) + { + PlanRowMark **markToRemove = &scratch->row_marks[toRemove->relid]; + PlanRowMark **markToKeep = &scratch->row_marks[toKeep->relid]; + + if (*markToKeep) + { + Assert((*markToKeep)->markType == (*markToRemove)->markType); + + root->rowMarks = list_delete_ptr(root->rowMarks, *markToKeep); + *markToKeep = NULL; + } + else + { + *markToKeep = *markToRemove; + *markToRemove = NULL; + + /* Shouldn't have inheritance children here. */ + Assert((*markToKeep)->rti == (*markToKeep)->prti); + + (*markToKeep)->rti = toKeep->relid; + (*markToKeep)->prti = toKeep->relid; + } + } + + /* + * Likewise replace references in SpecialJoinInfo data structures. + * + * This is relevant in case the join we're deleting is nested inside + * some special joins: the upper joins' relid sets have to be adjusted. + */ + foreach (cell, root->join_info_list) + { + SpecialJoinInfo *sjinfo = (SpecialJoinInfo *) lfirst(cell); + + change_relid(&sjinfo->min_lefthand, toRemove->relid, toKeep->relid); + change_relid(&sjinfo->min_righthand, toRemove->relid, toKeep->relid); + change_relid(&sjinfo->syn_lefthand, toRemove->relid, toKeep->relid); + change_relid(&sjinfo->syn_righthand, toRemove->relid, toKeep->relid); + change_varno((Expr*)sjinfo->semi_rhs_exprs, toRemove->relid, toKeep->relid); + } + + /* + * Likewise update references in PlaceHolderVar data structures. + */ + foreach(cell, root->placeholder_list) + { + PlaceHolderInfo *phinfo = (PlaceHolderInfo *) lfirst(cell); + + /* + * We are at an inner join of two base relations. A placeholder + * can't be needed here or evaluated here. + */ + Assert(!bms_is_subset(phinfo->ph_eval_at, joinrelids)); + Assert(!bms_is_subset(phinfo->ph_needed, joinrelids)); + + change_relid(&phinfo->ph_eval_at, toRemove->relid, toKeep->relid); + change_relid(&phinfo->ph_needed, toRemove->relid, toKeep->relid); + change_relid(&phinfo->ph_lateral, toRemove->relid, toKeep->relid); + change_relid(&phinfo->ph_var->phrels, toRemove->relid, toKeep->relid); + change_varno((Expr*)phinfo->ph_var, toRemove->relid, toKeep->relid); + } + + /* + * Update the equivalence classes that reference the removed relations. + */ + foreach(cell, root->eq_classes) + { + EquivalenceClass *ec = lfirst(cell); + + if (!bms_is_member(toRemove->relid, ec->ec_relids)) + { + /* + * This EC doesn't reference the removed relation, + * nothing to be done for it. + */ + continue; + } + + /* + * Update the EC members to reference the remaining relation + * instead of the removed one. + */ + update_ec_members(ec, toRemove->relid, toKeep->relid); + change_relid(&ec->ec_relids, toRemove->relid, toKeep->relid); + + /* + * We will now update source and derived clauses of the EC. + * + * Restriction clauses for base relations are already distributed + * to the respective baserestrictinfo lists (see + * generate_implied_equalities). The above code has already + * processed this list, and updated these clauses to reference + * the remaining relation, so we can skip them here based on their + * relids. + * + * Likewise, we have already processed the join clauses that join + * the removed relation to the remaining one. + * + * Finally, there are join clauses that join the removed relation + * to some third relation. We could delete just delete them and + * generate on demand, but sometimes we can't do this because there + * is no suitable equality operator (see the handling of ec_broken). + * In such cases we are going to use the source clauses, so we have + * to correct them too. + * + * Derived clauses can be generated again, so it is simpler to just + * delete them. + */ + list_free(ec->ec_derives); + ec->ec_derives = NIL; + update_ec_sources(&ec->ec_sources, toRemove->relid, toKeep->relid); + } + + /* + * Mark the rel as "dead" to show it is no longer part of the join tree. + * (Removing it from the baserel array altogether seems too risky.) + */ + toRemove->reloptkind = RELOPT_DEADREL; + + /* + * Update references to the removed relation from other baserels. + */ + for (i = 1; i < root->simple_rel_array_size; i++) + { + RelOptInfo *otherrel = root->simple_rel_array[i]; + int attroff; + + /* no point in processing target rel itself */ + if (i == toRemove->relid) + continue; + + /* there may be empty slots corresponding to non-baserel RTEs */ + if (otherrel == NULL) + continue; + + Assert(otherrel->relid == i); /* sanity check on array */ + + /* Update attr_needed arrays. */ + for (attroff = otherrel->max_attr - otherrel->min_attr; + attroff >= 0; attroff--) + { + change_relid(&otherrel->attr_needed[attroff], toRemove->relid, + toKeep->relid); + } + + /* Update lateral references. */ + change_varno((Expr*)otherrel->lateral_vars, toRemove->relid, toKeep->relid); + } + + /* + * Change varno in some special cases with non-trivial RangeTblEntry + */ + foreach(cell, root->parse->rtable) + { + RangeTblEntry *rte = lfirst(cell); + + switch(rte->rtekind) + { + case RTE_FUNCTION: + change_varno((Expr*)rte->functions, toRemove->relid, toKeep->relid); + break; + case RTE_TABLEFUNC: + change_varno((Expr*)rte->tablefunc, toRemove->relid, toKeep->relid); + break; + case RTE_VALUES: + change_varno((Expr*)rte->values_lists, toRemove->relid, toKeep->relid); + break; + default: + /* no op */ + break; + } + } +} + +/* + * Test whether the relations are joined on the same unique column. + */ +static bool +is_unique_self_join(PlannerInfo *root, Relids joinrelids, RelOptInfo *outer, + RelOptInfo *inner, List *restrictlist) +{ + UniqueIndexInfo *outeridx = NULL; + UniqueIndexInfo *inneridx = NULL; + ListCell *outerCell, *innerCell; + + innerrel_is_unique(root, joinrelids, inner->relids, + outer, JOIN_INNER, list_concat(list_concat_unique(list_copy(outer->joininfo), restrictlist), + outer->baserestrictinfo), true, &outeridx); + if (!outeridx) + return false; + + innerrel_is_unique(root, joinrelids, outer->relids, + inner, JOIN_INNER, list_concat(list_concat_unique(list_copy(inner->joininfo), restrictlist), + inner->baserestrictinfo), true, &inneridx); + if (!inneridx) + return false; + + /* We must have the same unique index for both relations. */ + if (outeridx->index->indexoid != inneridx->index->indexoid) + return false; + + if (restrictlist != NULL && IsA(((RestrictInfo*)linitial(restrictlist))->clause, Const)) + return false; + + /* + * The clauses that make a relation unique must be the same for both + * relations, or else we won't match the same row on each side of join. + * + * The lists of matching clauses are ordered as the index columns, so we + * just compare the list elements one by one. The varnos are different, + * so we copy the clauses and replace all mentions of outer varno with the + * inner, so that we can use equal(). + */ + forboth(innerCell, inneridx->clauses, outerCell, outeridx->clauses) + { + Expr *innerExpr = copyObject(castNode(RestrictInfo, lfirst(innerCell))->clause); + Expr *outerExpr = copyObject(castNode(RestrictInfo, lfirst(outerCell))->clause); + change_varno(outerExpr, outer->relid, inner->relid); + change_varno(innerExpr, outer->relid, inner->relid); + if (!equal(outerExpr, innerExpr)) + { + pfree(outerExpr); + pfree(innerExpr); + return false; + } + pfree(outerExpr); + pfree(innerExpr); + } + + return true; +} + +static bool +check_selfjoin(List* join_info_list, Index *relids, int i, int j) +{ + ListCell *cell; + + foreach(cell, join_info_list) + { + SpecialJoinInfo *info = (SpecialJoinInfo *) lfirst(cell); + + if (bms_is_member(relids[i], info->syn_lefthand) && + !bms_is_member(relids[j], info->syn_lefthand)) + return true; + + if (bms_is_member(relids[i], info->syn_righthand) && + !bms_is_member(relids[j], info->syn_righthand)) + return true; + + if (bms_is_member(relids[j], info->syn_lefthand) && + !bms_is_member(relids[i], info->syn_lefthand)) + return true; + + if (bms_is_member(relids[j], info->syn_righthand) && + !bms_is_member(relids[i], info->syn_righthand)) + return true; + } + + return false; +} + +/* + * Find and remove unique self joins in a group of base relations that have + * the same Oid. + * + * Returns a set of relids that were removed. + */ +static Relids +remove_self_joins_one_group(UsjScratch *scratch, Index *relids, int n) +{ + PlannerInfo *root = scratch->root; + Relids joinrelids = scratch->joinrelids; + Relids result = NULL; + int i, o; + + if (n < 2) + return NULL; + + for (o = 0; o < n; o++) + { + RelOptInfo *outer = root->simple_rel_array[relids[o]]; + + if (outer->reloptkind == RELOPT_DEADREL) + continue; + + for (i = o + 1; i < n; i++) + { + RelOptInfo *inner = root->simple_rel_array[relids[i]]; + List *restrictlist; + ListCell *cell; + + /* A sanity check: the relations have the same Oid. */ + Assert(root->simple_rte_array[relids[i]]->relid + == root->simple_rte_array[relids[o]]->relid); + + if (inner->reloptkind == RELOPT_DEADREL) + continue; + + /* + * This optimization applies to inner joins only, so skip any relations + * that form a special join. + */ + if (bms_is_member(relids[i], scratch->special_join_rels[relids[o]])) + continue; + + if (check_selfjoin(root->join_info_list, relids, i, o)) + continue; + + /* Reuse joinrelids bitset to avoid reallocation. */ + joinrelids = bms_del_members(joinrelids, joinrelids); + + /* + * We only deal with base rels here, so their relids bitset + * contains only one member -- their relid. + */ + joinrelids = bms_add_member(joinrelids, relids[o]); + joinrelids = bms_add_member(joinrelids, relids[i]); + + /* Is it a unique self join? */ + restrictlist = build_joinrel_restrictlist(root, joinrelids, outer, inner); + if (!is_unique_self_join(root, joinrelids, outer, inner, + restrictlist)) + continue; + + /* + * We can't remove the join if the relations have row marks of + * different strength (e.g. one is locked FOR UPDATE and another + * just has ROW_MARK_REFERENCE for EvalPlanQual rechecking). + */ + if (scratch->row_marks[relids[i]] && scratch->row_marks[relids[o]] + && scratch->row_marks[relids[i]]->markType + != scratch->row_marks[relids[o]]->markType) + { + continue; + } + + foreach(cell, root->placeholder_list) + { + PlaceHolderInfo *phinfo = (PlaceHolderInfo *) lfirst(cell); + + /* there isn't any other place to eval PHV */ + if (bms_is_subset(phinfo->ph_eval_at, joinrelids) || + bms_is_subset(phinfo->ph_needed, joinrelids)) + break; + } + + if (cell) + continue; + + /* + * We can remove either relation, so remove the outer one, + * to simplify this loop. + */ + remove_self_join_rel(scratch, joinrelids, restrictlist, inner, outer); + result = bms_add_member(result, relids[o]); + + /* + * Replace varno in root targetlist and HAVING clause. + */ + change_varno((Expr *) scratch->targetlist, relids[o], relids[i]); + change_varno((Expr *) root->parse->havingQual, relids[o], relids[i]); + + /* We removed the outer relation, try the next one. */ + break; + } + } + + scratch->joinrelids = joinrelids; + return result; +} + +/* + * A qsort comparator to sort the relids by the relation Oid. + */ +static int +compare_rte(const Index *left, const Index *right, PlannerInfo *root) +{ + Oid l = root->simple_rte_array[*left]->relid; + Oid r = root->simple_rte_array[*right]->relid; + return l < r ? 1 : ( l == r ? 0 : -1 ); +} + +/* + * Find and remove unique self joins on a particular level of the join tree. + * + * We sort the relations by Oid and then examine each group with the same Oid. + * If we removed any relation, remove it from joinlist as well. + */ +static void +remove_self_joins_one_level(UsjScratch *scratch, List *joinlist, + Relids *relidsToRemove) +{ + ListCell *cell; + Oid groupOid; + int groupStart; + int i; + int n = 0; + Index *relid_ascending = scratch->relids; + PlannerInfo *root = scratch->root; + + /* + * Collect the ids of base relations at this level of the join tree. + */ + foreach (cell, joinlist) + { + RangeTblEntry *rte; + RelOptInfo *rel; + RangeTblRef *ref = (RangeTblRef *) lfirst(cell); + if (!IsA(ref, RangeTblRef)) + continue; + + rte = root->simple_rte_array[ref->rtindex]; + rel = root->simple_rel_array[ref->rtindex]; + + /* We only care about base relations from which we select something. */ + if (rte->rtekind != RTE_RELATION || rte->relkind != RELKIND_RELATION + || rel == NULL) + { + continue; + } + + /* This optimization won't work for tables that have inheritance children. */ + if (rte->inh) + continue; + + relid_ascending[n++] = ref->rtindex; + + /* Limit the number of joins we process to control the quadratic behavior. */ + if (n > join_collapse_limit) + break; + } + + if (n < 2) + return; + + /* + * Find and process the groups of relations that have same Oid. + */ + qsort_arg(relid_ascending, n, sizeof(*relid_ascending), + (qsort_arg_comparator) compare_rte, root); + groupOid = root->simple_rte_array[relid_ascending[0]]->relid; + groupStart = 0; + for (i = 1; i < n; i++) + { + RangeTblEntry *rte = root->simple_rte_array[relid_ascending[i]]; + Assert(rte->relid != InvalidOid); + if (rte->relid != groupOid) + { + *relidsToRemove = bms_add_members(*relidsToRemove, + remove_self_joins_one_group(scratch, &relid_ascending[groupStart], + i - groupStart)); + groupOid = rte->relid; + groupStart = i; + } + } + Assert(groupOid != InvalidOid); + Assert(groupStart < n); + *relidsToRemove = bms_add_members(*relidsToRemove, + remove_self_joins_one_group(scratch, &relid_ascending[groupStart], + n - groupStart)); + + return; +} + +/* + * Find and remove unique self joins on a single level of a join tree, and + * recurse to handle deeper levels. + */ +static void +remove_self_joins_recurse(UsjScratch *scratch, List *joinlist, + Relids *relidsToRemove) +{ + ListCell *lc; + foreach (lc, joinlist) + { + switch (((Node*) lfirst(lc))->type) + { + case T_List: + remove_self_joins_recurse(scratch, (List *) lfirst(lc), + relidsToRemove); + break; + case T_RangeTblRef: + break; + default: + Assert(false); + } + } + + remove_self_joins_one_level(scratch, joinlist, relidsToRemove); +} + +bool enable_self_join_removal; + +/* + * Find and remove useless self joins. + * + * We search for joins where the same relation is joined to itself on all + * columns of some unique index. If this condition holds, then, for + * each outer row, only one inner row matches, and it is the same row + * of the same relation. This allows us to remove the join and replace + * it with a scan that combines WHERE clauses from both sides. The join + * clauses themselves assume the form of X = X and can be replaced with + * NOT NULL clauses. + * + * For the sake of simplicity, we don't apply this optimization to special + * joins. Here is a list of what we could do in some particular cases: + * 'a a1 semi join a a2': is reduced to inner by reduce_unique_semijoins, + * and then removed normally. + * 'a a1 anti join a a2': could simplify to a scan with 'outer quals AND + * (IS NULL on join columns OR NOT inner quals)'. + * 'a a1 left join a a2': could simplify to a scan like inner, but without + * NOT NULL condtions on join columns. + * 'a a1 left join (a a2 join b)': can't simplify this, because join to b + * can both remove rows and introduce duplicates. + * + * To search for removable joins, we order all the relations on their Oid, + * go over each set with the same Oid, and consider each pair of relations + * in this set. We check that both relation are made unique by the same + * unique index with the same clauses. + * + * To remove the join, we mark one of the participating relation as + * dead, and rewrite all references to it to point to the remaining + * relation. This includes modifying RestrictInfos, EquivalenceClasses and + * EquivalenceMembers. We also have to modify the row marks. The join clauses + * of the removed relation become either restriction or join clauses, based on + * whether they reference any relations not participating in the removed join. + * + * 'targetlist' is the top-level targetlist of query. If it has any references + * to the removed relations, we update them to point to the remaining ones. + */ +void +remove_useless_self_joins(PlannerInfo *root, List **joinlist, List *targetlist) +{ + ListCell *lc; + UsjScratch scratch; + Relids relidsToRemove = NULL; + int nremoved = 0, + relid = -1; + + if (!enable_self_join_removal) + return; + + scratch.root = root; + scratch.relids = palloc(root->simple_rel_array_size * sizeof(Index)); + scratch.special_join_rels = palloc0(root->simple_rel_array_size * sizeof(Relids)); + scratch.row_marks = palloc0(root->simple_rel_array_size * sizeof(PlanRowMark *)); + scratch.joinrelids = NULL; + scratch.targetlist = targetlist; + + /* Find out which relations have special joins to which. */ + foreach(lc, root->join_info_list) + { + SpecialJoinInfo *info = (SpecialJoinInfo *) lfirst(lc); + int bit = -1; + while ((bit = bms_next_member(info->syn_lefthand, bit)) >= 0) + { + RelOptInfo *rel = find_base_rel(root, bit); + scratch.special_join_rels[rel->relid] = + bms_add_members(scratch.special_join_rels[rel->relid], + info->syn_righthand); + } + + bit = -1; + while ((bit = bms_next_member(info->syn_righthand, bit)) >= 0) + { + RelOptInfo *rel = find_base_rel(root, bit); + scratch.special_join_rels[rel->relid] = + bms_add_members(scratch.special_join_rels[rel->relid], + info->syn_lefthand); + } + } + + /* Collect row marks. */ + foreach (lc, root->rowMarks) + { + PlanRowMark *rowMark = (PlanRowMark *) lfirst(lc); + + /* Can't have more than one row mark for a relation. */ + Assert(scratch.row_marks[rowMark->rti] == NULL); + + scratch.row_marks[rowMark->rti] = rowMark; + } + + /* Finally, remove the joins. */ + remove_self_joins_recurse(&scratch, *joinlist, &relidsToRemove); + while ((relid = bms_next_member(relidsToRemove, relid)) >= 0) + *joinlist = remove_rel_from_joinlist(*joinlist, relid, &nremoved); } diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index 917713c1633..8497addd00a 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -163,7 +163,6 @@ static List *fix_indexorderby_references(PlannerInfo *root, IndexPath *index_pat static Node *fix_indexqual_clause(PlannerInfo *root, IndexOptInfo *index, int indexcol, Node *clause, List *indexcolnos); -static Node *fix_indexqual_operand(Node *node, IndexOptInfo *index, int indexcol); static List *get_switched_clauses(List *clauses, Relids outerrelids); static List *order_qual_clauses(PlannerInfo *root, List *clauses); static void copy_generic_path_info(Plan *dest, Path *src); @@ -1119,6 +1118,7 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path, int flags) /* Generate a Result plan with constant-FALSE gating qual */ Plan *plan; + tlist = build_path_tlist(root, &best_path->path); plan = (Plan *) make_result(tlist, (Node *) list_make1(makeBoolConst(false, false)), @@ -1147,7 +1147,7 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path, int flags) plan->plan.righttree = NULL; plan->apprelids = rel->relids; - if (pathkeys != NIL) + if (pathkeys != NIL && best_path->pull_tlist == false) { /* * Compute sort column info, and adjust the Append's tlist as needed. @@ -1176,11 +1176,17 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path, int flags) /* Must insist that all children return the same tlist */ subplan = create_plan_recurse(root, subpath, CP_EXACT_TLIST); + if (tlist == NIL && best_path->pull_tlist) + plan->plan.targetlist = tlist = copyObject(subplan->targetlist); + /* * For ordered Appends, we must insert a Sort node if subplan isn't * sufficiently ordered. + * if best_path->pull_tlist = then plan came from + * keybased_rewrite_index_paths() which guarantee correct sorting in + * subplan */ - if (pathkeys != NIL) + if (pathkeys != NIL && best_path->pull_tlist == false) { int numsortkeys; AttrNumber *sortColIdx; @@ -4744,7 +4750,8 @@ replace_nestloop_params_mutator(Node *node, PlannerInfo *root) } return expression_tree_mutator(node, replace_nestloop_params_mutator, - (void *) root); + (void *) root, + 0); } /* @@ -4908,7 +4915,7 @@ fix_indexqual_clause(PlannerInfo *root, IndexOptInfo *index, int indexcol, * Most of the code here is just for sanity cross-checking that the given * expression actually matches the index column it's claimed to. */ -static Node * +Node * fix_indexqual_operand(Node *node, IndexOptInfo *index, int indexcol) { Var *result; diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c index 62dfc6d44a8..3e71a7d18a2 100644 --- a/src/backend/optimizer/plan/planmain.c +++ b/src/backend/optimizer/plan/planmain.c @@ -218,7 +218,7 @@ query_planner(PlannerInfo *root, * jointree preprocessing, but the necessary information isn't available * until we've built baserel data structures and classified qual clauses. */ - joinlist = remove_useless_joins(root, joinlist); + joinlist = remove_useless_left_joins(root, joinlist); /* * Also, reduce any semijoins with unique inner rels to plain inner joins. @@ -226,6 +226,11 @@ query_planner(PlannerInfo *root, */ reduce_unique_semijoins(root); + /* + * Remove self joins on a unique column. + */ + remove_useless_self_joins(root, &joinlist, root->processed_tlist); + /* * Now distribute "placeholders" to base rels as needed. This has to be * done after join removal because removal could change whether a diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 821693c60ee..d4f91e64aeb 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -268,11 +268,22 @@ planner(Query *parse, const char *query_string, int cursorOptions, ParamListInfo boundParams) { PlannedStmt *result; + instr_time planstart, + planduration; + + INSTR_TIME_SET_CURRENT(planstart); if (planner_hook) result = (*planner_hook) (parse, query_string, cursorOptions, boundParams); else result = standard_planner(parse, query_string, cursorOptions, boundParams); + + INSTR_TIME_SET_CURRENT(planduration); + INSTR_TIME_SUBTRACT(planduration, planstart); + + /* Record time spent on hooks and standard_planner() */ + result->planDuration = planduration; + return result; } @@ -1733,7 +1744,7 @@ inheritance_planner(PlannerInfo *root) /* Make a dummy path, cf set_dummy_rel_pathlist() */ dummy_path = (Path *) create_append_path(NULL, final_rel, NIL, NIL, NIL, NULL, 0, false, - NIL, -1); + NIL, -1, false); /* These lists must be nonempty to make a valid ModifyTable node */ subpaths = list_make1(dummy_path); @@ -4023,7 +4034,8 @@ create_degenerate_grouping_paths(PlannerInfo *root, RelOptInfo *input_rel, 0, false, NIL, - -1); + -1, + false); } else { @@ -6504,21 +6516,44 @@ add_paths_to_grouping_rel(PlannerInfo *root, RelOptInfo *input_rel, Path *path = (Path *) lfirst(lc); Path *path_original = path; bool is_sorted; - int presorted_keys; + int presorted_keys = 0; + List *group_pathkeys = root->group_pathkeys, + *group_clauses = parse->groupClause; + int n_preordered_groups = 0; - is_sorted = pathkeys_count_contained_in(root->group_pathkeys, - path->pathkeys, - &presorted_keys); + if (parse->groupingSets) + { + is_sorted = pathkeys_count_contained_in(group_pathkeys, + path->pathkeys, + &presorted_keys); + } + else + { + n_preordered_groups = + group_keys_reorder_by_pathkeys(path->pathkeys, + &group_pathkeys, + &group_clauses); + is_sorted = (n_preordered_groups == + list_length(group_pathkeys)); + } if (path == cheapest_path || is_sorted) { /* Sort the cheapest-total path if it isn't already sorted */ if (!is_sorted) + { + if (!parse->groupingSets) + get_cheapest_group_keys_order(root, + path->rows, + &group_pathkeys, + &group_clauses, + n_preordered_groups); path = (Path *) create_sort_path(root, grouped_rel, path, - root->group_pathkeys, + group_pathkeys, -1.0); + } /* Now decide what to stick atop it */ if (parse->groupingSets) @@ -6538,14 +6573,14 @@ add_paths_to_grouping_rel(PlannerInfo *root, RelOptInfo *input_rel, grouped_rel, path, grouped_rel->reltarget, - parse->groupClause ? AGG_SORTED : AGG_PLAIN, + group_clauses ? AGG_SORTED : AGG_PLAIN, AGGSPLIT_SIMPLE, - parse->groupClause, + group_clauses, havingQual, agg_costs, dNumGroups)); } - else if (parse->groupClause) + else if (group_clauses) { /* * We have GROUP BY without aggregation or grouping sets. @@ -6555,7 +6590,7 @@ add_paths_to_grouping_rel(PlannerInfo *root, RelOptInfo *input_rel, create_group_path(root, grouped_rel, path, - parse->groupClause, + group_clauses, havingQual, dNumGroups)); } @@ -6651,12 +6686,22 @@ add_paths_to_grouping_rel(PlannerInfo *root, RelOptInfo *input_rel, Path *path = (Path *) lfirst(lc); Path *path_original = path; bool is_sorted; - int presorted_keys; + int presorted_keys = 0; + List *group_pathkeys = root->group_pathkeys, + *group_clauses = parse->groupClause; + int n_preordered_groups; - is_sorted = pathkeys_count_contained_in(root->group_pathkeys, + is_sorted = pathkeys_count_contained_in(group_pathkeys, path->pathkeys, &presorted_keys); + n_preordered_groups = + group_keys_reorder_by_pathkeys(path->pathkeys, + &group_pathkeys, + &group_clauses); + is_sorted = is_sorted || + (n_preordered_groups == list_length(group_pathkeys)); + /* * Insert a Sort node, if required. But there's no point in * sorting anything but the cheapest path. @@ -6665,10 +6710,15 @@ add_paths_to_grouping_rel(PlannerInfo *root, RelOptInfo *input_rel, { if (path != partially_grouped_rel->cheapest_total_path) continue; + get_cheapest_group_keys_order(root, + path->rows, + &group_pathkeys, + &group_clauses, + n_preordered_groups); path = (Path *) create_sort_path(root, grouped_rel, path, - root->group_pathkeys, + group_pathkeys, -1.0); } @@ -6678,9 +6728,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)); @@ -6689,7 +6739,7 @@ add_paths_to_grouping_rel(PlannerInfo *root, RelOptInfo *input_rel, create_group_path(root, grouped_rel, path, - parse->groupClause, + group_clauses, havingQual, dNumGroups)); @@ -6954,18 +7004,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 *) @@ -6973,9 +7036,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)); @@ -6984,7 +7047,7 @@ create_partial_grouping_paths(PlannerInfo *root, create_group_path(root, partially_grouped_rel, path, - parse->groupClause, + group_clauses, NIL, dNumPartialGroups)); } @@ -7057,21 +7120,37 @@ create_partial_grouping_paths(PlannerInfo *root, Path *path = (Path *) lfirst(lc); Path *path_original = path; bool is_sorted; - int presorted_keys; + int presorted_keys = 0; + List *group_pathkeys = root->group_pathkeys, + *group_clauses = parse->groupClause; + int n_preordered_groups; - is_sorted = pathkeys_count_contained_in(root->group_pathkeys, + is_sorted = pathkeys_count_contained_in(group_pathkeys, path->pathkeys, &presorted_keys); + n_preordered_groups = group_keys_reorder_by_pathkeys(path->pathkeys, + &group_pathkeys, + &group_clauses); + is_sorted = is_sorted || (n_preordered_groups == list_length(group_pathkeys)); + if (path == cheapest_partial_path || is_sorted) { /* Sort the cheapest partial path, if it isn't already */ if (!is_sorted) + { + get_cheapest_group_keys_order(root, + path->rows, + &group_pathkeys, + &group_clauses, + n_preordered_groups); + path = (Path *) create_sort_path(root, partially_grouped_rel, path, - root->group_pathkeys, + group_pathkeys, -1.0); + } if (parse->hasAggs) add_partial_path(partially_grouped_rel, (Path *) @@ -7079,9 +7158,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)); @@ -7090,7 +7169,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 7bf90a98cb4..84b17a81987 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -1783,7 +1783,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 @@ -2117,7 +2117,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); } /* @@ -2474,6 +2474,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) { @@ -2488,6 +2492,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, @@ -2557,7 +2564,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); } /* @@ -2678,7 +2685,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 f912c6b5eb3..81ca1688572 100644 --- a/src/backend/optimizer/plan/subselect.c +++ b/src/backend/optimizer/plan/subselect.c @@ -714,7 +714,7 @@ convert_testexpr_mutator(Node *node, } return expression_tree_mutator(node, convert_testexpr_mutator, - (void *) context); + (void *) context, 0); } /* @@ -1872,7 +1872,7 @@ replace_correlation_vars_mutator(Node *node, PlannerInfo *root) } return expression_tree_mutator(node, replace_correlation_vars_mutator, - (void *) root); + (void *) root, 0); } /* @@ -2020,7 +2020,7 @@ process_sublinks_mutator(Node *node, process_sublinks_context *context) return expression_tree_mutator(node, process_sublinks_mutator, - (void *) &locContext); + (void *) &locContext, 0); } /* diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index e04ddf86cd5..949fc90a52d 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -620,7 +620,7 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root, * Append the child results together. */ path = (Path *) create_append_path(root, result_rel, pathlist, NIL, - NIL, NULL, 0, false, NIL, -1); + NIL, NULL, 0, false, NIL, -1,false); /* * For UNION ALL, we just need the Append path. For UNION, need to add @@ -677,7 +677,7 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root, create_append_path(root, result_rel, NIL, partial_pathlist, NIL, NULL, parallel_workers, enable_parallel_append, - NIL, -1); + NIL, -1,false); ppath = (Path *) create_gather_path(root, result_rel, ppath, result_rel->reltarget, NULL, NULL); @@ -787,7 +787,7 @@ generate_nonunion_paths(SetOperationStmt *op, PlannerInfo *root, * Append the child results together. */ path = (Path *) create_append_path(root, result_rel, pathlist, NIL, - NIL, NULL, 0, false, NIL, -1); + NIL, NULL, 0, false, NIL, -1,false); /* Identify the grouping semantics */ groupList = generate_setop_grouplist(op, tlist); diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c index d722063cf3b..45df729f7ca 100644 --- a/src/backend/optimizer/util/appendinfo.c +++ b/src/backend/optimizer/util/appendinfo.c @@ -385,7 +385,7 @@ adjust_appendrel_attrs_mutator(Node *node, j = (JoinExpr *) expression_tree_mutator(node, adjust_appendrel_attrs_mutator, - (void *) context); + (void *) context, 0); /* now fix JoinExpr's rtindex (probably never happens) */ for (cnt = 0; cnt < nappinfos; cnt++) { @@ -406,7 +406,7 @@ adjust_appendrel_attrs_mutator(Node *node, phv = (PlaceHolderVar *) expression_tree_mutator(node, adjust_appendrel_attrs_mutator, - (void *) context); + (void *) context, 0); /* now fix PlaceHolderVar's relid sets */ if (phv->phlevelsup == 0) phv->phrels = adjust_child_relids(phv->phrels, context->nappinfos, @@ -488,7 +488,7 @@ adjust_appendrel_attrs_mutator(Node *node, Assert(!IsA(node, Query)); return expression_tree_mutator(node, adjust_appendrel_attrs_mutator, - (void *) context); + (void *) context, 0); } /* diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index 10843fb3800..87727411837 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -2366,7 +2366,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. @@ -2494,7 +2494,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, @@ -2638,7 +2638,7 @@ eval_const_expressions_mutator(Node *node, */ args = (List *) expression_tree_mutator((Node *) expr->args, eval_const_expressions_mutator, - (void *) context); + (void *) context, 0); /* * We must do our own check for NULLs because DistinctExpr has @@ -3968,7 +3968,7 @@ simplify_function(Oid funcid, Oid result_type, int32 result_typmod, args = expand_function_arguments(args, 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; } @@ -4748,7 +4748,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); } /* @@ -5188,5 +5188,5 @@ substitute_actual_srf_parameters_mutator(Node *node, } return expression_tree_mutator(node, substitute_actual_srf_parameters_mutator, - (void *) context); + (void *) context, 0); } diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c index a490b204e6a..8dc9b1ad2e9 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; @@ -1217,7 +1229,8 @@ create_append_path(PlannerInfo *root, List *subpaths, List *partial_subpaths, List *pathkeys, Relids required_outer, int parallel_workers, bool parallel_aware, - List *partitioned_rels, double rows) + List *partitioned_rels, double rows, + bool pull_tlist) { AppendPath *pathnode = makeNode(AppendPath); ListCell *l; @@ -1251,6 +1264,7 @@ create_append_path(PlannerInfo *root, pathnode->path.parallel_workers = parallel_workers; pathnode->path.pathkeys = pathkeys; pathnode->partitioned_rels = list_copy(partitioned_rels); + pathnode->pull_tlist = pull_tlist; /* * For parallel append, non-partial paths are sorted by descending total @@ -1315,7 +1329,7 @@ create_append_path(PlannerInfo *root, pathnode->path.pathkeys = child->pathkeys; } else - cost_append(pathnode); + cost_append(pathnode, root); /* If the caller provided a row estimate, override the computed value. */ if (rows >= 0) @@ -1629,7 +1643,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; @@ -3743,9 +3758,13 @@ adjust_limit_rows_costs(double *rows, /* in/out parameter */ if (count_rows > *rows) count_rows = *rows; if (input_rows > 0) + { + *startup_cost = *startup_cost + + 2*(input_total_cost - input_startup_cost) / input_rows; *total_cost = *startup_cost + (input_total_cost - input_startup_cost) * count_rows / input_rows; + } *rows = count_rows; if (*rows < 1) *rows = 1; @@ -3862,7 +3881,8 @@ reparameterize_path(PlannerInfo *root, Path *path, apath->path.parallel_workers, apath->path.parallel_aware, apath->partitioned_rels, - -1); + -1, + apath->pull_tlist); } default: break; diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index f7ce44cb22a..c211d29f9d2 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -443,6 +443,9 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, index_close(indexRelation, NoLock); + info->sslots = palloc0( + (STATISTIC_NUM_SLOTS + 1) * sizeof(AttStatsSlot)); + /* * We've historically used lcons() here. It'd make more sense to * use lappend(), but that causes the planner to change behavior diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index a203e6f1ff5..70cbbdd12ad 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -40,14 +40,10 @@ typedef struct JoinHashEntry static void build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel, RelOptInfo *input_rel); -static List *build_joinrel_restrictlist(PlannerInfo *root, - RelOptInfo *joinrel, - RelOptInfo *outer_rel, - RelOptInfo *inner_rel); static void build_joinrel_joinlist(RelOptInfo *joinrel, RelOptInfo *outer_rel, RelOptInfo *inner_rel); -static List *subbuild_joinrel_restrictlist(RelOptInfo *joinrel, +static List *subbuild_joinrel_restrictlist(Relids joinrelids, List *joininfo_list, List *new_restrictlist); static List *subbuild_joinrel_joinlist(RelOptInfo *joinrel, @@ -600,7 +596,7 @@ build_join_rel(PlannerInfo *root, */ if (restrictlist_ptr) *restrictlist_ptr = build_joinrel_restrictlist(root, - joinrel, + joinrel->relids, outer_rel, inner_rel); return joinrel; @@ -706,7 +702,7 @@ build_join_rel(PlannerInfo *root, * caller might or might not need the restrictlist, but I need it anyway * for set_joinrel_size_estimates().) */ - restrictlist = build_joinrel_restrictlist(root, joinrel, + restrictlist = build_joinrel_restrictlist(root, joinrel->relids, outer_rel, inner_rel); if (restrictlist_ptr) *restrictlist_ptr = restrictlist; @@ -1041,7 +1037,7 @@ build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel, * the various joinlist entries ultimately refer to RestrictInfos * pushed into them by distribute_restrictinfo_to_rels(). * - * 'joinrel' is a join relation node + * 'joinrelids' is a join relation id set * 'outer_rel' and 'inner_rel' are a pair of relations that can be joined * to form joinrel. * @@ -1054,9 +1050,9 @@ build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel, * RestrictInfo nodes are no longer context-dependent. Instead, just include * the original nodes in the lists made for the join relation. */ -static List * +List * build_joinrel_restrictlist(PlannerInfo *root, - RelOptInfo *joinrel, + Relids joinrelids, RelOptInfo *outer_rel, RelOptInfo *inner_rel) { @@ -1067,8 +1063,8 @@ build_joinrel_restrictlist(PlannerInfo *root, * eliminating any duplicates (important since we will see many of the * same clauses arriving from both input relations). */ - result = subbuild_joinrel_restrictlist(joinrel, outer_rel->joininfo, NIL); - result = subbuild_joinrel_restrictlist(joinrel, inner_rel->joininfo, result); + result = subbuild_joinrel_restrictlist(joinrelids, outer_rel->joininfo, NIL); + result = subbuild_joinrel_restrictlist(joinrelids, inner_rel->joininfo, result); /* * Add on any clauses derived from EquivalenceClasses. These cannot be @@ -1077,7 +1073,7 @@ build_joinrel_restrictlist(PlannerInfo *root, */ result = list_concat(result, generate_join_implied_equalities(root, - joinrel->relids, + joinrelids, outer_rel->relids, inner_rel)); @@ -1102,18 +1098,66 @@ build_joinrel_joinlist(RelOptInfo *joinrel, joinrel->joininfo = result; } +typedef struct UniquePtrList { + List *unique_list; + HTAB *h; +} UniquePtrList; + +static void +addUniquePtrList(UniquePtrList *upl, void *v) +{ + if (upl->h != NULL || list_length(upl->unique_list) > 32) + { + bool found; + + if (upl->h == NULL) + { + HASHCTL hash_ctl; + ListCell *l; + + MemSet(&hash_ctl, 0, sizeof(hash_ctl)); + + hash_ctl.keysize = sizeof(void*); + hash_ctl.entrysize = sizeof(void*); + + upl->h = hash_create("UniquePtrList storage", 64, &hash_ctl, + HASH_BLOBS | HASH_ELEM); + + foreach(l, upl->unique_list) + { + void *k = lfirst(l); + + hash_search(upl->h, &k, HASH_ENTER, &found); + Assert(found == false); + } + } + + hash_search(upl->h, &v, HASH_ENTER, &found); + if (found == false) + upl->unique_list = lappend(upl->unique_list, v); + } + else + { + upl->unique_list = list_append_unique_ptr(upl->unique_list, v); + } +} + static List * -subbuild_joinrel_restrictlist(RelOptInfo *joinrel, +subbuild_joinrel_restrictlist(Relids joinrelids, List *joininfo_list, List *new_restrictlist) { ListCell *l; + UniquePtrList upl; + + memset(&upl, 0, sizeof(upl)); + upl.unique_list = new_restrictlist; foreach(l, joininfo_list) { RestrictInfo *rinfo = (RestrictInfo *) lfirst(l); - if (bms_is_subset(rinfo->required_relids, joinrel->relids)) + if (bms_is_subset(rinfo->required_relids, joinrelids)) { /* * This clause becomes a restriction clause for the joinrel, since @@ -1122,7 +1166,7 @@ subbuild_joinrel_restrictlist(RelOptInfo *joinrel, * different joinlists will have been multiply-linked rather than * copied, pointer equality should be a sufficient test.) */ - new_restrictlist = list_append_unique_ptr(new_restrictlist, rinfo); + addUniquePtrList(&upl, rinfo); } else { @@ -1133,7 +1177,8 @@ subbuild_joinrel_restrictlist(RelOptInfo *joinrel, } } - return new_restrictlist; + hash_destroy(upl.h); + return upl.unique_list; } static List * @@ -1142,6 +1187,10 @@ subbuild_joinrel_joinlist(RelOptInfo *joinrel, List *new_joininfo) { ListCell *l; + UniquePtrList upl; + + memset(&upl, 0, sizeof(upl)); + upl.unique_list = new_joininfo; /* Expected to be called only for join between parent relations. */ Assert(joinrel->reloptkind == RELOPT_JOINREL); @@ -1167,11 +1216,12 @@ subbuild_joinrel_joinlist(RelOptInfo *joinrel, * multiply-linked rather than copied, pointer equality should be * a sufficient test.) */ - new_joininfo = list_append_unique_ptr(new_joininfo, rinfo); + addUniquePtrList(&upl, rinfo); } } - return new_joininfo; + hash_destroy(upl.h); + return upl.unique_list; } diff --git a/src/backend/optimizer/util/var.c b/src/backend/optimizer/util/var.c index 109a0d978fd..0682f300f1b 100644 --- a/src/backend/optimizer/util/var.c +++ b/src/backend/optimizer/util/var.c @@ -844,7 +844,7 @@ flatten_join_alias_vars_mutator(Node *node, phv = (PlaceHolderVar *) expression_tree_mutator(node, flatten_join_alias_vars_mutator, - (void *) context); + (void *) context, 0); /* now fix PlaceHolderVar's relid sets */ if (phv->phlevelsup == context->sublevels_up) { @@ -880,7 +880,7 @@ flatten_join_alias_vars_mutator(Node *node, Assert(!IsA(node, MinMaxAggInfo)); return expression_tree_mutator(node, flatten_join_alias_vars_mutator, - (void *) context); + (void *) context, 0); } /* diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 635d17dcea6..776602acce4 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -4469,7 +4469,7 @@ DropTableSpaceStmt: DROP TABLESPACE name * * QUERY: * CREATE EXTENSION extension - * [ WITH ] [ SCHEMA schema ] [ VERSION version ] + * [ WITH ] [ SCHEMA schema ] [ VERSION version ] [ FROM oldversion ] * *****************************************************************************/ @@ -4509,10 +4509,7 @@ create_extension_opt_item: } | FROM NonReservedWord_or_Sconst { - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("CREATE EXTENSION ... FROM is no longer supported"), - parser_errposition(@1))); + $$ = makeDefElem("old_version", (Node *)makeString($2), @1); } | CASCADE { @@ -13246,7 +13243,7 @@ a_expr: c_expr { $$ = $1; } } | a_expr LIKE a_expr ESCAPE a_expr %prec LIKE { - FuncCall *n = makeFuncCall(SystemFuncName("like_escape"), + FuncCall *n = makeFuncCall(list_make1(makeString("like_escape")), list_make2($3, $5), @2); $$ = (Node *) makeSimpleA_Expr(AEXPR_LIKE, "~~", @@ -13259,7 +13256,7 @@ a_expr: c_expr { $$ = $1; } } | a_expr NOT_LA LIKE a_expr ESCAPE a_expr %prec NOT_LA { - FuncCall *n = makeFuncCall(SystemFuncName("like_escape"), + FuncCall *n = makeFuncCall(list_make1(makeString("like_escape")), list_make2($4, $6), @2); $$ = (Node *) makeSimpleA_Expr(AEXPR_LIKE, "!~~", @@ -13272,7 +13269,7 @@ a_expr: c_expr { $$ = $1; } } | a_expr ILIKE a_expr ESCAPE a_expr %prec ILIKE { - FuncCall *n = makeFuncCall(SystemFuncName("like_escape"), + FuncCall *n = makeFuncCall(list_make1(makeString("like_escape")), list_make2($3, $5), @2); $$ = (Node *) makeSimpleA_Expr(AEXPR_ILIKE, "~~*", @@ -13285,7 +13282,7 @@ a_expr: c_expr { $$ = $1; } } | a_expr NOT_LA ILIKE a_expr ESCAPE a_expr %prec NOT_LA { - FuncCall *n = makeFuncCall(SystemFuncName("like_escape"), + FuncCall *n = makeFuncCall(list_make1(makeString("like_escape")), list_make2($4, $6), @2); $$ = (Node *) makeSimpleA_Expr(AEXPR_ILIKE, "!~~*", @@ -13294,7 +13291,7 @@ a_expr: c_expr { $$ = $1; } | a_expr SIMILAR TO a_expr %prec SIMILAR { - FuncCall *n = makeFuncCall(SystemFuncName("similar_to_escape"), + FuncCall *n = makeFuncCall(list_make1(makeString("similar_to_escape")), list_make1($4), @2); $$ = (Node *) makeSimpleA_Expr(AEXPR_SIMILAR, "~", @@ -13302,7 +13299,7 @@ a_expr: c_expr { $$ = $1; } } | a_expr SIMILAR TO a_expr ESCAPE a_expr %prec SIMILAR { - FuncCall *n = makeFuncCall(SystemFuncName("similar_to_escape"), + FuncCall *n = makeFuncCall(list_make1(makeString("similar_to_escape")), list_make2($4, $6), @2); $$ = (Node *) makeSimpleA_Expr(AEXPR_SIMILAR, "~", @@ -13310,7 +13307,7 @@ a_expr: c_expr { $$ = $1; } } | a_expr NOT_LA SIMILAR TO a_expr %prec NOT_LA { - FuncCall *n = makeFuncCall(SystemFuncName("similar_to_escape"), + FuncCall *n = makeFuncCall(list_make1(makeString("similar_to_escape")), list_make1($5), @2); $$ = (Node *) makeSimpleA_Expr(AEXPR_SIMILAR, "!~", @@ -13318,7 +13315,7 @@ a_expr: c_expr { $$ = $1; } } | a_expr NOT_LA SIMILAR TO a_expr ESCAPE a_expr %prec NOT_LA { - FuncCall *n = makeFuncCall(SystemFuncName("similar_to_escape"), + FuncCall *n = makeFuncCall(list_make1(makeString("similar_to_escape")), list_make2($5, $7), @2); $$ = (Node *) makeSimpleA_Expr(AEXPR_SIMILAR, "!~", diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c index c6a328c1103..dc15f29c251 100644 --- a/src/backend/parser/parse_clause.c +++ b/src/backend/parser/parse_clause.c @@ -1198,6 +1198,7 @@ transformFromClauseItem(ParseState *pstate, Node *n, &r_namespace); /* Remove the left-side RTEs from the namespace list again */ + pstate->p_namespace = list_truncate(pstate->p_namespace, sv_namespace_length); diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c index 7d40f4ce372..8476d7ddf57 100644 --- a/src/backend/parser/parse_relation.c +++ b/src/backend/parser/parse_relation.c @@ -2142,7 +2142,6 @@ addRangeTableEntryForJoin(ParseState *pstate, { RangeTblEntry *rte = makeNode(RangeTblEntry); Alias *eref; - int numaliases; ParseNamespaceItem *nsitem; Assert(pstate != NULL); @@ -2167,19 +2166,37 @@ addRangeTableEntryForJoin(ParseState *pstate, rte->joinrightcols = rightcols; rte->alias = alias; - eref = alias ? copyObject(alias) : makeAlias("unnamed_join", NIL); - numaliases = list_length(eref->colnames); - /* fill in any unspecified alias columns */ - if (numaliases < list_length(colnames)) - eref->colnames = list_concat(eref->colnames, - list_copy_tail(colnames, numaliases)); + if (alias) + { + int numaliases; - if (numaliases > list_length(colnames)) - ereport(ERROR, - (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), - errmsg("join expression \"%s\" has %d columns available but %d columns specified", - eref->aliasname, list_length(colnames), numaliases))); + eref = copyObject(alias); + + numaliases = list_length(eref->colnames); + + if (numaliases == 0) + { + eref->colnames = colnames; + } + else if (numaliases > 0 && numaliases < list_length(colnames)) + { + eref->colnames = list_concat(eref->colnames, + list_copy_tail(colnames, numaliases)); + list_free(colnames); + } + if (numaliases > list_length(colnames)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), + errmsg("join expression \"%s\" has %d columns available but %d columns specified", + eref->aliasname, list_length(colnames), numaliases))); + + } + else + { + eref = makeAlias("unnamed_join", NIL); + eref->colnames = colnames; + } rte->eref = eref; @@ -2785,8 +2802,11 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up, { char *label = strVal(lfirst(colname)); - *colnames = lappend(*colnames, - makeString(pstrdup(label))); + /* + * Assume label is already pstrdup'ed somewhere, so + * don't duplicate it again + */ + *colnames = lappend(*colnames, makeString(label)); } if (colvars) diff --git a/src/backend/rewrite/rewriteManip.c b/src/backend/rewrite/rewriteManip.c index 506959b7b67..7e19675796c 100644 --- a/src/backend/rewrite/rewriteManip.c +++ b/src/backend/rewrite/rewriteManip.c @@ -1118,7 +1118,7 @@ replace_rte_variables(Node *node, int target_varno, int sublevels_up, result = query_or_expression_tree_mutator(node, replace_rte_variables_mutator, (void *) &context, - 0); + QTW_DONT_COPY_DEFAULT); if (context.inserted_sublink) { @@ -1188,14 +1188,15 @@ replace_rte_variables_mutator(Node *node, newnode = query_tree_mutator((Query *) node, replace_rte_variables_mutator, (void *) context, - 0); + QTW_DONT_COPY_DEFAULT); newnode->hasSubLinks |= context->inserted_sublink; context->inserted_sublink = save_inserted_sublink; context->sublevels_up--; return (Node *) newnode; } - return expression_tree_mutator(node, replace_rte_variables_mutator, - (void *) context); + + return expression_tree_mutator(node, replace_rte_variables_mutator, + (void *) context, QTW_DONT_COPY_DEFAULT); } @@ -1352,7 +1353,7 @@ map_variable_attnos_mutator(Node *node, return (Node *) newnode; } return expression_tree_mutator(node, map_variable_attnos_mutator, - (void *) context); + (void *) context, 0); } Node * diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c index 452ab88cafa..0a3057c041d 100644 --- a/src/backend/storage/file/fd.c +++ b/src/backend/storage/file/fd.c @@ -882,6 +882,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; @@ -901,6 +902,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 (;;) { @@ -916,12 +926,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; } @@ -943,6 +953,10 @@ count_usable_fds(int max_to_probe, int *usable_fds, int *already_open) for (j = 0; j < used; j++) close(fd[j]); +#ifdef WIN32 + if (fdTest>0) + _close(fdTest); +#endif pfree(fd); /* diff --git a/src/backend/storage/ipc/sinval.c b/src/backend/storage/ipc/sinval.c index b5640e46be0..14aa1b9ffcc 100644 --- a/src/backend/storage/ipc/sinval.c +++ b/src/backend/storage/ipc/sinval.c @@ -15,6 +15,7 @@ #include "postgres.h" #include "access/xact.h" +#include "access/xlog.h" #include "commands/async.h" #include "miscadmin.h" #include "storage/ipc.h" @@ -48,6 +49,17 @@ volatile sig_atomic_t catchupInterruptPending = false; void SendSharedInvalidMessages(const SharedInvalidationMessage *msgs, int n) { + int i; + + for(i=0; i<n; i++) + { + /* sync with SIInsertDataEntries() */ + if ((msgs[i].id == SHAREDINVALRELCACHE_ID && + msgs[i].rc.isLocal == true) || + (msgs[i].id >= 0 && msgs[i].cc.isLocal == true)) + LocalExecuteInvalidationMessage(((SharedInvalidationMessage*)msgs) + i); + } + SIInsertDataEntries(msgs, n); } @@ -67,6 +79,8 @@ SendSharedInvalidMessages(const SharedInvalidationMessage *msgs, int n) * and counters; it's so that a recursive call can process messages already * sucked out of sinvaladt.c. */ + +static int ReceiveSharedInvalidMessagesDepth = 0; void ReceiveSharedInvalidMessages(void (*invalFunction) (SharedInvalidationMessage *msg), void (*resetFunction) (void)) @@ -81,6 +95,11 @@ ReceiveSharedInvalidMessages(void (*invalFunction) (SharedInvalidationMessage *m static volatile int nextmsg = 0; static volatile int nummsgs = 0; + if (ReceiveSharedInvalidMessagesDepth) + return; + + ReceiveSharedInvalidMessagesDepth++; + /* Deal with any messages still pending from an outer recursion */ while (nextmsg < nummsgs) { @@ -102,7 +121,6 @@ ReceiveSharedInvalidMessages(void (*invalFunction) (SharedInvalidationMessage *m if (getResult < 0) { /* got a reset message */ - elog(DEBUG4, "cache state reset"); SharedInvalidMessageCounter++; resetFunction(); break; /* nothing more to do */ @@ -140,6 +158,9 @@ ReceiveSharedInvalidMessages(void (*invalFunction) (SharedInvalidationMessage *m elog(DEBUG4, "sinval catchup complete, cleaning queue"); SICleanupQueue(false, 0); } + + Assert(ReceiveSharedInvalidMessagesDepth == 1); + ReceiveSharedInvalidMessagesDepth--; } diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index e5c115b92f2..5e382c3ba20 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -18,6 +18,7 @@ #include <unistd.h> #include "access/transam.h" +#include "access/xlog.h" #include "miscadmin.h" #include "storage/backendid.h" #include "storage/ipc.h" @@ -127,8 +128,8 @@ * per iteration. */ -#define MAXNUMMESSAGES 4096 -#define MSGNUMWRAPAROUND (MAXNUMMESSAGES * 262144) +#define MAXNUMMESSAGES 16384 +#define MSGNUMWRAPAROUND (MAXNUMMESSAGES * 65536) #define CLEANUP_MIN (MAXNUMMESSAGES / 2) #define CLEANUP_QUANTUM (MAXNUMMESSAGES / 16) #define SIG_THRESHOLD (MAXNUMMESSAGES / 2) @@ -479,8 +480,16 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) max = segP->maxMsgNum; while (nthistime-- > 0) { - segP->buffer[max % MAXNUMMESSAGES] = *data++; - max++; + /* sync with SendSharedInvalidMessages() */ + if (!((data->id == SHAREDINVALRELCACHE_ID && + data->rc.isLocal == true) || ( + data->id >= 0 && data->cc.isLocal == true))) + { + segP->buffer[max % MAXNUMMESSAGES] = *data; + max++; + } + + data++; } /* Update current value of maxMsgNum using spinlock */ diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c index db89137c033..49423ea4d22 100644 --- a/src/backend/storage/lmgr/lwlock.c +++ b/src/backend/storage/lmgr/lwlock.c @@ -998,6 +998,15 @@ LWLockWakeup(LWLock *lock) wokeup_somebody = true; } + /* + * Signal that the process isn't on the wait list anymore. This allows + * LWLockDequeueSelf() to remove itself of the waitlist with a + * proclist_delete(), rather than having to check if it has been + * removed from the list. + */ + Assert(waiter->lwWaiting == LW_WS_WAITING); + waiter->lwWaiting = LW_WS_PENDING_WAKEUP; + /* * Once we've woken up an exclusive lock, there's no point in waking * up anybody else. @@ -1055,7 +1064,7 @@ LWLockWakeup(LWLock *lock) * another lock. */ pg_write_barrier(); - waiter->lwWaiting = false; + waiter->lwWaiting = LW_WS_NOT_WAITING; PGSemaphoreUnlock(waiter->sem); } } @@ -1076,7 +1085,7 @@ LWLockQueueSelf(LWLock *lock, LWLockMode mode) if (MyProc == NULL) elog(PANIC, "cannot wait without a PGPROC structure"); - if (MyProc->lwWaiting) + if (MyProc->lwWaiting != LW_WS_NOT_WAITING) elog(PANIC, "queueing for lock while waiting on another one"); LWLockWaitListLock(lock); @@ -1084,7 +1093,7 @@ LWLockQueueSelf(LWLock *lock, LWLockMode mode) /* setting the flag is protected by the spinlock */ pg_atomic_fetch_or_u32(&lock->state, LW_FLAG_HAS_WAITERS); - MyProc->lwWaiting = true; + MyProc->lwWaiting = LW_WS_WAITING; MyProc->lwWaitMode = mode; /* LW_WAIT_UNTIL_FREE waiters are always at the front of the queue */ @@ -1112,8 +1121,7 @@ LWLockQueueSelf(LWLock *lock, LWLockMode mode) static void LWLockDequeueSelf(LWLock *lock) { - bool found = false; - proclist_mutable_iter iter; + bool on_waitlist; #ifdef LWLOCK_STATS lwlock_stats *lwstats; @@ -1126,18 +1134,13 @@ LWLockDequeueSelf(LWLock *lock) LWLockWaitListLock(lock); /* - * Can't just remove ourselves from the list, but we need to iterate over - * all entries as somebody else could have dequeued us. + * Remove ourselves from the waitlist, unless we've already been + * removed. The removal happens with the wait list lock held, so there's + * no race in this check. */ - proclist_foreach_modify(iter, &lock->waiters, lwWaitLink) - { - if (iter.cur == MyProc->pgprocno) - { - found = true; - proclist_delete(&lock->waiters, iter.cur, lwWaitLink); - break; - } - } + on_waitlist = MyProc->lwWaiting == LW_WS_WAITING; + if (on_waitlist) + proclist_delete(&lock->waiters, MyProc->pgprocno, lwWaitLink); if (proclist_is_empty(&lock->waiters) && (pg_atomic_read_u32(&lock->state) & LW_FLAG_HAS_WAITERS) != 0) @@ -1149,8 +1152,8 @@ LWLockDequeueSelf(LWLock *lock) LWLockWaitListUnlock(lock); /* clear waiting state again, nice for debugging */ - if (found) - MyProc->lwWaiting = false; + if (on_waitlist) + MyProc->lwWaiting = LW_WS_NOT_WAITING; else { int extraWaits = 0; @@ -1174,7 +1177,7 @@ LWLockDequeueSelf(LWLock *lock) for (;;) { PGSemaphoreLock(MyProc->sem); - if (!MyProc->lwWaiting) + if (MyProc->lwWaiting == LW_WS_NOT_WAITING) break; extraWaits++; } @@ -1325,7 +1328,7 @@ LWLockAcquire(LWLock *lock, LWLockMode mode) for (;;) { PGSemaphoreLock(proc->sem); - if (!proc->lwWaiting) + if (proc->lwWaiting == LW_WS_NOT_WAITING) break; extraWaits++; } @@ -1490,7 +1493,7 @@ LWLockAcquireOrWait(LWLock *lock, LWLockMode mode) for (;;) { PGSemaphoreLock(proc->sem); - if (!proc->lwWaiting) + if (proc->lwWaiting == LW_WS_NOT_WAITING) break; extraWaits++; } @@ -1706,7 +1709,7 @@ LWLockWaitForVar(LWLock *lock, uint64 *valptr, uint64 oldval, uint64 *newval) for (;;) { PGSemaphoreLock(proc->sem); - if (!proc->lwWaiting) + if (proc->lwWaiting == LW_WS_NOT_WAITING) break; extraWaits++; } @@ -1787,6 +1790,10 @@ LWLockUpdateVar(LWLock *lock, uint64 *valptr, uint64 val) proclist_delete(&lock->waiters, iter.cur, lwWaitLink); proclist_push_tail(&wakeup, iter.cur, lwWaitLink); + + /* see LWLockWakeup() */ + Assert(waiter->lwWaiting == LW_WS_WAITING); + waiter->lwWaiting = LW_WS_PENDING_WAKEUP; } /* We are done updating shared state of the lock itself. */ @@ -1802,7 +1809,7 @@ LWLockUpdateVar(LWLock *lock, uint64 *valptr, uint64 val) proclist_delete(&wakeup, iter.cur, lwWaitLink); /* check comment in LWLockWakeup() about this barrier */ pg_write_barrier(); - waiter->lwWaiting = false; + waiter->lwWaiting = LW_WS_NOT_WAITING; PGSemaphoreUnlock(waiter->sem); } } diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c index 199c0eae078..81a2025eb50 100644 --- a/src/backend/storage/lmgr/proc.c +++ b/src/backend/storage/lmgr/proc.c @@ -402,7 +402,7 @@ InitProcess(void) /* NB -- autovac launcher intentionally does not set IS_AUTOVACUUM */ if (IsAutoVacuumWorkerProcess()) MyPgXact->vacuumFlags |= PROC_IS_AUTOVACUUM; - MyProc->lwWaiting = false; + MyProc->lwWaiting = LW_WS_NOT_WAITING; MyProc->lwWaitMode = 0; MyProc->waitLock = NULL; MyProc->waitProcLock = NULL; @@ -582,7 +582,7 @@ InitAuxiliaryProcess(void) MyProc->delayChkpt = false; MyProc->delayChkptEnd = false; MyPgXact->vacuumFlags = 0; - MyProc->lwWaiting = false; + MyProc->lwWaiting = LW_WS_NOT_WAITING; MyProc->lwWaitMode = 0; MyProc->waitLock = NULL; MyProc->waitProcLock = NULL; diff --git a/src/backend/storage/smgr/smgr.c b/src/backend/storage/smgr/smgr.c index 7d667c6586f..d6caf8b6872 100644 --- a/src/backend/storage/smgr/smgr.c +++ b/src/backend/storage/smgr/smgr.c @@ -427,7 +427,8 @@ smgrdounlinkall(SMgrRelation *rels, int nrels, bool isRedo) * closed our own smgr rel. */ for (i = 0; i < nrels; i++) - CacheInvalidateSmgr(rnodes[i]); + if (!SmgrIsTemp(rels[i])) + CacheInvalidateSmgr(rnodes[i]); /* * Delete the physical file(s). @@ -571,7 +572,8 @@ smgrtruncate(SMgrRelation reln, ForkNumber *forknum, int nforks, BlockNumber *nb * is a performance-critical path.) As in the unlink code, we want to be * sure the message is sent before we start changing things on-disk. */ - CacheInvalidateSmgr(reln->smgr_rnode); + if (!SmgrIsTemp(reln)) + CacheInvalidateSmgr(reln->smgr_rnode); /* Do the truncation */ for (i = 0; i < nforks; i++) diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index 1c8c7b95dad..77bf3b49392 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -73,6 +73,7 @@ #include "tcop/pquery.h" #include "tcop/tcopprot.h" #include "tcop/utility.h" +#include "utils/builtins.h" #include "utils/lsyscache.h" #include "utils/memutils.h" #include "utils/ps_status.h" @@ -3108,6 +3109,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 */ @@ -4324,6 +4327,7 @@ PostgresMain(int argc, char *argv[], */ CHECK_FOR_INTERRUPTS(); DoingCommandRead = false; + enable_timeout_after(SKIP_CLIENT_CHECK_TIMEOUT, 1000); /* * (6) check for any other interesting events that happened while we diff --git a/src/backend/utils/adt/like_support.c b/src/backend/utils/adt/like_support.c index ed4d6a8dc67..0fdec4f2372 100644 --- a/src/backend/utils/adt/like_support.c +++ b/src/backend/utils/adt/like_support.c @@ -55,20 +55,9 @@ #include "utils/selfuncs.h" #include "utils/varlena.h" - -typedef enum -{ - Pattern_Type_Like, - Pattern_Type_Like_IC, - Pattern_Type_Regex, - Pattern_Type_Regex_IC, - Pattern_Type_Prefix -} Pattern_Type; - -typedef enum -{ - Pattern_Prefix_None, Pattern_Prefix_Partial, Pattern_Prefix_Exact -} Pattern_Prefix_Status; +#include "catalog/pg_proc.h" +#include "utils/catcache.h" +#include "utils/syscache.h" static Node *like_regex_support(Node *rawreq, Pattern_Type ptype); static List *match_pattern_prefix(Node *leftop, @@ -108,6 +97,119 @@ static Datum string_to_datum(const char *str, Oid datatype); static Const *string_to_const(const char *str, Oid datatype); static Const *string_to_bytea_const(const char *str, size_t str_len); +/**************************************************************************** + * ---- ROUTINES FOR "SPECIAL" INDEXABLE OPERATORS FOR + * SPECIAL USER_DEFINED TYPES ---- + * -- teodor + ****************************************************************************/ + +static Oid mmPFPOid = InvalidOid; +static Oid mmGTOid = InvalidOid; +static Oid mcharOid = InvalidOid; +static Oid mvarcharOid = InvalidOid; + +#define HeapTupleGetOid(type, tuple) (((type)GETSTRUCT(tuple))->oid) + +static Oid +findTypeOid(char *typname) +{ + CatCList *catlist; + HeapTuple tup; + int n_members; + Oid typoid; + + catlist = SearchSysCacheList(TYPENAMENSP, 1, + CStringGetDatum(typname), 0, 0); + + n_members = catlist->n_members; + + if (n_members != 1) + { + ReleaseSysCacheList(catlist); + if (n_members > 1) + elog(ERROR,"There are %d candidates for '%s' type", + n_members, typname); + return InvalidOid; + } + + tup = &catlist->members[0]->tuple; + + typoid = HeapTupleGetOid(Form_pg_type, tup); + + ReleaseSysCacheList(catlist); + + return typoid; +} + +static bool +fillMCharOIDS() { + CatCList *catlist; + HeapTuple tup; + char *funcname = "mchar_pattern_fixed_prefix"; + int n_members; + + catlist = SearchSysCacheList(PROCNAMEARGSNSP, 1, + CStringGetDatum(funcname), 0, 0); + n_members = catlist->n_members; + + if (n_members != 1) { + ReleaseSysCacheList(catlist); + if (n_members > 1) + elog(ERROR,"There are %d candidates for '%s' function'", n_members, funcname); + return false; + } + + tup = &catlist->members[0]->tuple; + + if ( HeapTupleGetOid(Form_pg_proc, tup) != mmPFPOid ) { + char *quals_funcname = "mchar_greaterstring"; + Oid tmp_mmPFPOid = HeapTupleGetOid(Form_pg_proc, tup); + + ReleaseSysCacheList(catlist); + + mcharOid = findTypeOid("mchar"); + mvarcharOid = findTypeOid("mvarchar"); + + if ( mcharOid == InvalidOid || mvarcharOid == InvalidOid ) { + elog(LOG,"Can't find mchar/mvarvarchar types: mchar=%d mvarchar=%d", + mcharOid, mvarcharOid); + return false; + } + + catlist = SearchSysCacheList(PROCNAMEARGSNSP, 1, + CStringGetDatum(quals_funcname), 0, 0); + n_members = catlist->n_members; + + if ( n_members != 1 ) { + ReleaseSysCacheList(catlist); + if ( n_members > 1 ) + elog(ERROR,"There are %d candidates for '%s' function'", n_members, quals_funcname); + return false; + } + + tup = &catlist->members[0]->tuple; + mmGTOid = HeapTupleGetOid(Form_pg_proc, tup); + mmPFPOid = tmp_mmPFPOid; + } + + ReleaseSysCacheList(catlist); + + return true; +} + +static Pattern_Prefix_Status +mchar_pattern_fixed_prefix(Const *patt, Pattern_Type ptype, Const **prefix) +{ + if (!fillMCharOIDS()) + return Pattern_Prefix_None; + + return (Pattern_Prefix_Status)DatumGetInt32( OidFunctionCall3( + mmPFPOid, + PointerGetDatum(patt), + Int32GetDatum(ptype), + PointerGetDatum(prefix) + ) ); +} /* * Planner support functions for LIKE, regex, and related operators @@ -251,6 +353,7 @@ match_pattern_prefix(Node *leftop, Expr *expr; FmgrInfo ltproc; Const *greaterstr; + bool isMchar = false; /* * Can't do anything with a non-constant or NULL pattern argument. @@ -283,8 +386,16 @@ match_pattern_prefix(Node *leftop, /* * Try to extract a fixed prefix from the pattern. */ - pstatus = pattern_fixed_prefix(patt, ptype, expr_coll, - &prefix, NULL); + ldatatype = exprType(leftop); + if (fillMCharOIDS() && (ldatatype == mcharOid || + ldatatype == mvarcharOid)) + { + pstatus = mchar_pattern_fixed_prefix(patt, ptype, &prefix); + isMchar = true; + } + else + pstatus = pattern_fixed_prefix(patt, ptype, expr_coll, + &prefix, NULL); /* fail if no fixed prefix */ if (pstatus == Pattern_Prefix_None) @@ -299,7 +410,6 @@ match_pattern_prefix(Node *leftop, * selected operators also determine the needed type of the prefix * constant. */ - ldatatype = exprType(leftop); switch (ldatatype) { case TEXTOID: @@ -358,7 +468,16 @@ match_pattern_prefix(Node *leftop, break; default: /* Can't get here unless we're attached to the wrong operator */ - return NIL; + if (!isMchar) + return NIL; + collation_aware = false; + rdatatype = mvarcharOid; + ltopr = get_opfamily_member(opfamily, ldatatype, rdatatype, + BTLessStrategyNumber); + eqopr = get_opfamily_member(opfamily, ldatatype, rdatatype, + BTEqualStrategyNumber); + geopr = get_opfamily_member(opfamily, ldatatype, rdatatype, + BTGreaterEqualStrategyNumber); } /* @@ -384,9 +503,10 @@ match_pattern_prefix(Node *leftop, */ if (prefix->consttype != rdatatype) { - Assert(prefix->consttype == TEXTOID && - rdatatype == BPCHAROID); - prefix->consttype = rdatatype; + Assert(isMchar || (prefix->consttype == TEXTOID && + rdatatype == BPCHAROID)); + if (!isMchar) + prefix->consttype = rdatatype; } /* @@ -432,7 +552,12 @@ match_pattern_prefix(Node *leftop, if (!op_in_opfamily(ltopr, opfamily)) return result; fmgr_info(get_opcode(ltopr), <proc); - greaterstr = make_greater_string(prefix, <proc, indexcollation); + if (isMchar) + greaterstr = (Const*)DatumGetPointer(OidFunctionCall1( + mmGTOid, + PointerGetDatum(prefix))); + else + greaterstr = make_greater_string(prefix, <proc, indexcollation); if (greaterstr) { expr = make_opclause(ltopr, BOOLOID, false, diff --git a/src/backend/utils/adt/rowtypes.c b/src/backend/utils/adt/rowtypes.c index 80cba2f4c26..8c562e2ce52 100644 --- a/src/backend/utils/adt/rowtypes.c +++ b/src/backend/utils/adt/rowtypes.c @@ -27,7 +27,6 @@ #include "utils/lsyscache.h" #include "utils/typcache.h" - /* * structure to cache metadata needed for record I/O */ @@ -786,6 +785,9 @@ record_cmp(FunctionCallInfo fcinfo) { HeapTupleHeader record1 = PG_GETARG_HEAPTUPLEHEADER(0); HeapTupleHeader record2 = PG_GETARG_HEAPTUPLEHEADER(1); + int record_cmp_prefix = + (PG_NARGS() == 3 && PG_GETARG_INT32(2) > 0) ? + PG_GETARG_INT32(2) : INT_MAX; int result = 0; Oid tupType1; Oid tupType2; @@ -870,6 +872,9 @@ record_cmp(FunctionCallInfo fcinfo) nulls2 = (bool *) palloc(ncolumns2 * sizeof(bool)); heap_deform_tuple(&tuple2, tupdesc2, values2, nulls2); + ncolumns1 = Min(ncolumns1, record_cmp_prefix); + ncolumns2 = Min(ncolumns2, record_cmp_prefix); + /* * Scan corresponding columns, allowing for dropped columns in different * places in the two rows. i1 and i2 are physical column indexes, j is @@ -1030,6 +1035,9 @@ record_eq(PG_FUNCTION_ARGS) { HeapTupleHeader record1 = PG_GETARG_HEAPTUPLEHEADER(0); HeapTupleHeader record2 = PG_GETARG_HEAPTUPLEHEADER(1); + int record_cmp_prefix = + (PG_NARGS() == 3 && PG_GETARG_INT32(2) > 0) ? + PG_GETARG_INT32(2) : INT_MAX; bool result = true; Oid tupType1; Oid tupType2; @@ -1114,6 +1122,9 @@ record_eq(PG_FUNCTION_ARGS) nulls2 = (bool *) palloc(ncolumns2 * sizeof(bool)); heap_deform_tuple(&tuple2, tupdesc2, values2, nulls2); + ncolumns1 = Min(ncolumns1, record_cmp_prefix); + ncolumns2 = Min(ncolumns2, record_cmp_prefix); + /* * Scan corresponding columns, allowing for dropped columns in different * places in the two rows. i1 and i2 are physical column indexes, j is diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index e91c375e698..12be5b69f62 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -284,6 +284,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 */ @@ -363,6 +365,7 @@ static void set_relation_column_names(deparse_namespace *dpns, deparse_columns *colinfo); static void set_join_column_names(deparse_namespace *dpns, RangeTblEntry *rte, deparse_columns *colinfo); +static void add_colname(deparse_columns *colinfo, char *colname); static bool colname_is_unique(const char *colname, deparse_namespace *dpns, deparse_columns *colinfo); static char *make_colname_unique(char *colname, deparse_namespace *dpns, @@ -4318,7 +4321,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]; @@ -4367,7 +4373,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]; @@ -4392,6 +4401,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? * @@ -4404,6 +4436,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++) { @@ -4455,6 +4556,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 37458da096d..5b3b9d210fb 100644 --- a/src/backend/utils/adt/selfuncs.c +++ b/src/backend/utils/adt/selfuncs.c @@ -149,13 +149,14 @@ get_relation_stats_hook_type get_relation_stats_hook = NULL; get_index_stats_hook_type get_index_stats_hook = NULL; static double eqsel_internal(PG_FUNCTION_ARGS, bool negate); -static double eqjoinsel_inner(Oid opfuncoid, Oid collation, +static double eqjoinsel_inner(Oid operator, Oid opfuncoid, Oid collation, VariableStatData *vardata1, VariableStatData *vardata2, double nd1, double nd2, bool isdefault1, bool isdefault2, AttStatsSlot *sslot1, AttStatsSlot *sslot2, Form_pg_statistic stats1, Form_pg_statistic stats2, - bool have_mcvs1, bool have_mcvs2); + bool have_mcvs1, bool have_mcvs2, + int record_cmp_prefix); static double eqjoinsel_semi(Oid opfuncoid, Oid collation, VariableStatData *vardata1, VariableStatData *vardata2, double nd1, double nd2, @@ -163,7 +164,8 @@ static double eqjoinsel_semi(Oid opfuncoid, Oid collation, AttStatsSlot *sslot1, AttStatsSlot *sslot2, Form_pg_statistic stats1, Form_pg_statistic stats2, bool have_mcvs1, bool have_mcvs2, - RelOptInfo *inner_rel); + RelOptInfo *inner_rel, + int record_cmp_prefix); static bool estimate_multivariate_ndistinct(PlannerInfo *root, RelOptInfo *rel, List **varinfos, double *ndistinct); static bool convert_to_scalar(Datum value, Oid valuetypid, Oid collid, @@ -215,6 +217,20 @@ static bool get_actual_variable_endpoint(Relation heapRel, Datum *endpointDatum); static RelOptInfo *find_join_input_rel(PlannerInfo *root, Relids relids); +static bool +join_is_reversed_variables(SpecialJoinInfo *sjinfo, + VariableStatData *vardata1, VariableStatData *vardata2) +{ + if (vardata1->rel && + bms_is_subset(vardata1->rel->relids, sjinfo->syn_righthand)) + return true; /* var1 is on RHS */ + else if (vardata2->rel && + bms_is_subset(vardata2->rel->relids, sjinfo->syn_lefthand)) + return true; /* var2 is on LHS */ + else + return false; +} + /* * eqsel - Selectivity of "=" for any data types. @@ -287,6 +303,34 @@ eqsel_internal(PG_FUNCTION_ARGS, bool negate) return selec; } +static bool +get_cached_attstatsslot(AttStatsSlot *sslot, VariableStatData *vardata, + int reqkind, Oid reqop, int flags) +{ + if (vardata->sslots) + { + /* + * vardata has somewhere cache + */ + AttStatsSlot *sslotp; + + sslotp = fill_attstatsslot(vardata->sslots, + vardata->statsTuple, + reqkind, reqop, flags); + + if (sslotp) + { + *sslot = *sslotp; + return true; + } + } + + return get_attstatsslot(sslot, vardata->statsTuple, + reqkind, reqop, + flags); +} + + /* * var_eq_const --- eqsel for var = const case * @@ -296,6 +340,15 @@ double var_eq_const(VariableStatData *vardata, Oid operator, Oid collation, Datum constval, bool constisnull, bool varonleft, bool negate) +{ + return eqconst_selectivity(operator, collation, vardata, constval, + constisnull, varonleft, negate, -1); +} + +Selectivity +eqconst_selectivity(Oid operator, Oid collation, + VariableStatData *vardata, Datum constval, bool constisnull, + bool varonleft, bool negate, int record_cmp_prefix) { double selec; double nullfrac = 0.0; @@ -328,7 +381,8 @@ var_eq_const(VariableStatData *vardata, Oid operator, Oid collation, * different from ours, but it's much more likely to be right than * ignoring the information.) */ - if (vardata->isunique && vardata->rel && vardata->rel->tuples >= 1.0) + if (vardata->isunique && vardata->rel && vardata->rel->tuples >= 1.0 && + record_cmp_prefix <= 0) { selec = 1.0 / vardata->rel->tuples; } @@ -347,11 +401,11 @@ var_eq_const(VariableStatData *vardata, Oid operator, Oid collation, * don't like this, maybe you shouldn't be using eqsel for your * operator...) */ - if (get_attstatsslot(&sslot, vardata->statsTuple, - STATISTIC_KIND_MCV, InvalidOid, - ATTSTATSSLOT_VALUES | ATTSTATSSLOT_NUMBERS)) + if (get_cached_attstatsslot(&sslot, vardata, + STATISTIC_KIND_MCV, InvalidOid, + ATTSTATSSLOT_VALUES | ATTSTATSSLOT_NUMBERS)) { - LOCAL_FCINFO(fcinfo, 2); + LOCAL_FCINFO(fcinfo, 3); FmgrInfo eqproc; fmgr_info(opfuncoid, &eqproc); @@ -362,15 +416,17 @@ var_eq_const(VariableStatData *vardata, Oid operator, Oid collation, * eqproc returns NULL, though really equality functions should * never do that. */ - InitFunctionCallInfoData(*fcinfo, &eqproc, 2, collation, + InitFunctionCallInfoData(*fcinfo, &eqproc, 3, collation, NULL, NULL); fcinfo->args[0].isnull = false; fcinfo->args[1].isnull = false; + fcinfo->args[2].isnull = false; /* be careful to apply operator right way 'round */ if (varonleft) fcinfo->args[1].value = constval; else fcinfo->args[0].value = constval; + fcinfo->args[2].value = Int32GetDatum(record_cmp_prefix); for (i = 0; i < sslot.nvalues; i++) { @@ -1020,6 +1076,138 @@ generic_restriction_selectivity(PlannerInfo *root, Oid oproid, Oid collation, return selec; } +/* + * Binary search of bound constval in histogramm + */ +static int +prefix_record_histogram_search(AttStatsSlot *sslot, int start, + Datum constval, int record_cmp_prefix, + FmgrInfo *opproc, bool isgt) +{ + int lobound = start; /* first possible slot to search */ + int hibound = sslot->nvalues; /* last+1 slot to search */ + + while (lobound < hibound) + { + int probe = (lobound + hibound) / 2; + bool ltcmp; + + ltcmp = DatumGetBool(FunctionCall3Coll(opproc, + DEFAULT_COLLATION_OID, + sslot->values[probe], + constval, + Int32GetDatum(record_cmp_prefix))); + if (isgt) + ltcmp = !ltcmp; + if (ltcmp) + lobound = probe + 1; + else + hibound = probe; + } + + return lobound; +} + +/* + * Simple function to estimate selctivity by prefix of record, it just counts + * number of histogram bins matched by record prefix - similar to + * histogram_selectivity() but it knows about sortability of record + */ +double +prefix_record_histogram_selectivity(VariableStatData *vardata, + Datum constvalLeft, Datum constvalRight, + int record_cmp_prefix, + double ndistinct,int *n_bins) +{ + double result = -1.0; + AttStatsSlot sslot; + + if (HeapTupleIsValid(vardata->statsTuple) && + get_cached_attstatsslot(&sslot, vardata, + STATISTIC_KIND_HISTOGRAM, InvalidOid, + ATTSTATSSLOT_VALUES)) + { + FmgrInfo opprocLT, opprocGT; + int start = -1, + end = -1; + + + if (sslot.nvalues > 2) + { + fmgr_info(F_RECORD_GE, &opprocGT); + fmgr_info(F_RECORD_LE, &opprocLT); + + start = prefix_record_histogram_search(&sslot, 0, constvalLeft, + record_cmp_prefix, + &opprocGT, true); + if (start < 0) + start = 0; + end = prefix_record_histogram_search(&sslot, start, constvalRight, + -1, + &opprocLT, false); + if (end >= sslot.nvalues) + end = sslot.nvalues - 1; + } + else + { + fmgr_info(F_RECORD_GT, &opprocGT); + fmgr_info(F_RECORD_LE, &opprocLT); + + /* + * Find first bin which start border is less than constant + */ + for (start = sslot.nvalues - 1; start >= 0; start--) + { + if (DatumGetBool(FunctionCall3Coll(&opprocGT, + DEFAULT_COLLATION_OID, + constvalLeft, + sslot.values[start], + Int32GetDatum(record_cmp_prefix)))) + break; + } + + if (start < 0) + start=0; + + /* + * Find last bin which end border is less than constant + */ + for (end = start; end <= sslot.nvalues - 2; end ++) + { + if (DatumGetBool(FunctionCall3Coll(&opprocLT, + DEFAULT_COLLATION_OID, + constvalRight, + sslot.values[end + 1], + Int32GetDatum(-1)))) + break; + } + } + + if (opprocGT.fn_extra) + pfree(opprocGT.fn_extra); + if (opprocLT.fn_extra) + pfree(opprocLT.fn_extra); + + *n_bins = (start >= end) ? 0 : end - start; + result = (start >= end) ? 0.5 : end - start; + result /= ((double) (sslot.nvalues)); + + free_attstatsslot(&sslot); + + if (*n_bins == 0 && ndistinct > 1) + { + double ntuples = vardata->rel->tuples; + double ntuplesbin = vardata->rel->tuples / sslot.nvalues; + + result *= (1 - pow((ntuples - ntuplesbin) / ntuples, + ntuples / ndistinct)); + } + } + + return result; + +} + /* * ineq_histogram_selectivity - Examine the histogram for scalarineqsel * @@ -2248,11 +2436,32 @@ eqjoinsel(PG_FUNCTION_ARGS) JoinType jointype = (JoinType) PG_GETARG_INT16(3); #endif SpecialJoinInfo *sjinfo = (SpecialJoinInfo *) PG_GETARG_POINTER(4); - Oid collation = PG_GET_COLLATION(); + VariableStatData vardata1; + VariableStatData vardata2; + Selectivity s; + Oid collation = PG_GET_COLLATION(); + + get_join_variables(root, args, sjinfo, + &vardata1, &vardata2, NULL); + + + s = eqjoin_selectivity(root, operator, collation, &vardata1, &vardata2, + sjinfo, -1); + + ReleaseVariableStats(vardata1); + ReleaseVariableStats(vardata2); + + PG_RETURN_FLOAT8((float8)s); +} + +Selectivity +eqjoin_selectivity(PlannerInfo *root, Oid operator, Oid collation, + VariableStatData* vardata1, + VariableStatData* vardata2, SpecialJoinInfo *sjinfo, + int record_cmp_prefix) +{ double selec; double selec_inner; - VariableStatData vardata1; - VariableStatData vardata2; double nd1; double nd2; bool isdefault1; @@ -2267,45 +2476,45 @@ eqjoinsel(PG_FUNCTION_ARGS) bool join_is_reversed; RelOptInfo *inner_rel; - get_join_variables(root, args, sjinfo, - &vardata1, &vardata2, &join_is_reversed); + join_is_reversed = join_is_reversed_variables(sjinfo, vardata1, vardata2); - nd1 = get_variable_numdistinct(&vardata1, &isdefault1); - nd2 = get_variable_numdistinct(&vardata2, &isdefault2); + nd1 = get_variable_numdistinct(vardata1, &isdefault1); + nd2 = get_variable_numdistinct(vardata2, &isdefault2); opfuncoid = get_opcode(operator); memset(&sslot1, 0, sizeof(sslot1)); memset(&sslot2, 0, sizeof(sslot2)); - if (HeapTupleIsValid(vardata1.statsTuple)) + if (HeapTupleIsValid(vardata1->statsTuple)) { /* note we allow use of nullfrac regardless of security check */ - stats1 = (Form_pg_statistic) GETSTRUCT(vardata1.statsTuple); - if (statistic_proc_security_check(&vardata1, opfuncoid)) - have_mcvs1 = get_attstatsslot(&sslot1, vardata1.statsTuple, - STATISTIC_KIND_MCV, InvalidOid, - ATTSTATSSLOT_VALUES | ATTSTATSSLOT_NUMBERS); + stats1 = (Form_pg_statistic) GETSTRUCT(vardata1->statsTuple); + if (statistic_proc_security_check(vardata1, opfuncoid)) + have_mcvs1 = get_cached_attstatsslot(&sslot1, vardata1, + STATISTIC_KIND_MCV, InvalidOid, + ATTSTATSSLOT_VALUES | ATTSTATSSLOT_NUMBERS); } - if (HeapTupleIsValid(vardata2.statsTuple)) + if (HeapTupleIsValid(vardata2->statsTuple)) { /* note we allow use of nullfrac regardless of security check */ - stats2 = (Form_pg_statistic) GETSTRUCT(vardata2.statsTuple); - if (statistic_proc_security_check(&vardata2, opfuncoid)) - have_mcvs2 = get_attstatsslot(&sslot2, vardata2.statsTuple, - STATISTIC_KIND_MCV, InvalidOid, - ATTSTATSSLOT_VALUES | ATTSTATSSLOT_NUMBERS); + stats2 = (Form_pg_statistic) GETSTRUCT(vardata2->statsTuple); + if (statistic_proc_security_check(vardata2, opfuncoid)) + have_mcvs2 = get_cached_attstatsslot(&sslot2, vardata2, + STATISTIC_KIND_MCV, InvalidOid, + ATTSTATSSLOT_VALUES | ATTSTATSSLOT_NUMBERS); } /* We need to compute the inner-join selectivity in all cases */ - selec_inner = eqjoinsel_inner(opfuncoid, collation, - &vardata1, &vardata2, + selec_inner = eqjoinsel_inner(operator, opfuncoid, collation, + vardata1, vardata2, nd1, nd2, isdefault1, isdefault2, &sslot1, &sslot2, stats1, stats2, - have_mcvs1, have_mcvs2); + have_mcvs1, have_mcvs2, + record_cmp_prefix); switch (sjinfo->jointype) { @@ -2327,26 +2536,28 @@ eqjoinsel(PG_FUNCTION_ARGS) if (!join_is_reversed) selec = eqjoinsel_semi(opfuncoid, collation, - &vardata1, &vardata2, + vardata1, vardata2, nd1, nd2, isdefault1, isdefault2, &sslot1, &sslot2, stats1, stats2, have_mcvs1, have_mcvs2, - inner_rel); + inner_rel, + record_cmp_prefix); else { Oid commop = get_commutator(operator); Oid commopfuncoid = OidIsValid(commop) ? get_opcode(commop) : InvalidOid; selec = eqjoinsel_semi(commopfuncoid, collation, - &vardata2, &vardata1, + vardata2, vardata1, nd2, nd1, isdefault2, isdefault1, &sslot2, &sslot1, stats2, stats1, have_mcvs2, have_mcvs1, - inner_rel); + inner_rel, + record_cmp_prefix); } /* @@ -2372,12 +2583,132 @@ eqjoinsel(PG_FUNCTION_ARGS) free_attstatsslot(&sslot1); free_attstatsslot(&sslot2); - ReleaseVariableStats(vardata1); - ReleaseVariableStats(vardata2); - CLAMP_PROBABILITY(selec); - PG_RETURN_FLOAT8((float8) selec); + return selec; +} + +static int +cmp_vardata(FmgrInfo *eqproc, FmgrInfo *ltproc, + Datum v1, Datum v2, int record_cmp_prefix) +{ + int cmp; + + cmp = DatumGetBool(FunctionCall3Coll(ltproc, + DEFAULT_COLLATION_OID, + v1, v2, + Int32GetDatum(record_cmp_prefix))); + + if (cmp) + return -1; + + cmp = DatumGetBool(FunctionCall3Coll(eqproc, + DEFAULT_COLLATION_OID, + v1, v2, + Int32GetDatum(record_cmp_prefix))); + + return !cmp; +} +static double +eqjoinsel_histogram(Oid eqop, + VariableStatData *vardata1, VariableStatData *vardata2, + int record_cmp_prefix, double nd1, double nd2) +{ + bool have_hist1 = false; + bool have_hist2 = false; + AttStatsSlot sslot1; + AttStatsSlot sslot2; + int i1 = 0, i2 = 0; + double n1 = 0.0, n2 = 0.0; + double result = -1.0; + FmgrInfo eqproc, ltproc; + Oid orderop = InvalidOid; + List *opfamilies; + ListCell *lc; + + if (!(HeapTupleIsValid(vardata1->statsTuple) && + HeapTupleIsValid(vardata2->statsTuple))) + return result; + + memset(&sslot1, 0, sizeof(sslot1)); + memset(&sslot2, 0, sizeof(sslot2)); + + have_hist1 = get_cached_attstatsslot(&sslot1, vardata1, + STATISTIC_KIND_HISTOGRAM, InvalidOid, + ATTSTATSSLOT_VALUES); + have_hist2 = get_cached_attstatsslot(&sslot2, vardata2, + STATISTIC_KIND_HISTOGRAM, InvalidOid, + ATTSTATSSLOT_VALUES); + + if (!(have_hist1 && have_hist2)) + goto out; + + opfamilies = get_mergejoin_opfamilies(eqop); + foreach(lc, opfamilies) { + Oid opf = lfirst_oid(lc); + + orderop = get_opfamily_member(opf, vardata1->vartype, vardata2->vartype, + BTLessStrategyNumber); + + if (OidIsValid(orderop)) + break; + } + + /* == from fulleq, for example */ + if (!OidIsValid(orderop)) + goto out; + + fmgr_info(get_opcode(eqop), &eqproc); + fmgr_info(get_opcode(orderop), <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; } /* @@ -2387,13 +2718,14 @@ eqjoinsel(PG_FUNCTION_ARGS) * that it's worth trying to distinguish them here. */ static double -eqjoinsel_inner(Oid opfuncoid, Oid collation, +eqjoinsel_inner(Oid operator, Oid opfuncoid, Oid collation, VariableStatData *vardata1, VariableStatData *vardata2, double nd1, double nd2, bool isdefault1, bool isdefault2, AttStatsSlot *sslot1, AttStatsSlot *sslot2, Form_pg_statistic stats1, Form_pg_statistic stats2, - bool have_mcvs1, bool have_mcvs2) + bool have_mcvs1, bool have_mcvs2, + int record_cmp_prefix) { double selec; @@ -2411,7 +2743,7 @@ eqjoinsel_inner(Oid opfuncoid, Oid collation, * results", Technical Report 1018, Computer Science Dept., University * of Wisconsin, Madison, March 1991 (available from ftp.cs.wisc.edu). */ - LOCAL_FCINFO(fcinfo, 2); + LOCAL_FCINFO(fcinfo, 3); FmgrInfo eqproc; bool *hasmatch1; bool *hasmatch2; @@ -2437,10 +2769,12 @@ eqjoinsel_inner(Oid opfuncoid, Oid collation, * returns NULL, though really equality functions should never do * that. */ - InitFunctionCallInfoData(*fcinfo, &eqproc, 2, collation, + InitFunctionCallInfoData(*fcinfo, &eqproc, 3, collation, NULL, NULL); fcinfo->args[0].isnull = false; fcinfo->args[1].isnull = false; + fcinfo->args[2].isnull = false; + fcinfo->args[2].value = Int32GetDatum(record_cmp_prefix); hasmatch1 = (bool *) palloc0(sslot1->nvalues * sizeof(bool)); hasmatch2 = (bool *) palloc0(sslot2->nvalues * sizeof(bool)); @@ -2566,11 +2900,34 @@ eqjoinsel_inner(Oid opfuncoid, Oid collation, double nullfrac1 = stats1 ? stats1->stanullfrac : 0.0; double nullfrac2 = stats2 ? stats2->stanullfrac : 0.0; - selec = (1.0 - nullfrac1) * (1.0 - nullfrac2); - if (nd1 > nd2) - selec /= nd1; - else - selec /= nd2; + if (isdefault1 && vardata1->rel && nd1 > vardata1->rel->rows) + { + nd1 = vardata1->rel->rows; + if (nd1 == 0.0) + nd1 = 1.0; + } + + if (isdefault2 && vardata2->rel && nd2 > vardata2->rel->rows) + { + nd2 = vardata2->rel->rows; + if (nd2 == 0.0) + nd2 = 1.0; + } + + selec = eqjoinsel_histogram(operator, vardata1, vardata2, + record_cmp_prefix, nd1, nd2); + + if (selec < 0) + { + selec = 1.0; + + if (nd1 > nd2) + selec /= nd1; + else + selec /= nd2; + } + + selec *= (1.0 - nullfrac1) * (1.0 - nullfrac2); } return selec; @@ -2591,7 +2948,8 @@ eqjoinsel_semi(Oid opfuncoid, Oid collation, AttStatsSlot *sslot1, AttStatsSlot *sslot2, Form_pg_statistic stats1, Form_pg_statistic stats2, bool have_mcvs1, bool have_mcvs2, - RelOptInfo *inner_rel) + RelOptInfo *inner_rel, + int record_cmp_prefix) { double selec; @@ -2638,7 +2996,7 @@ eqjoinsel_semi(Oid opfuncoid, Oid collation, * lists. We still have to estimate for the remaining population, but * in a skewed distribution this gives us a big leg up in accuracy. */ - LOCAL_FCINFO(fcinfo, 2); + LOCAL_FCINFO(fcinfo, 3); FmgrInfo eqproc; bool *hasmatch1; bool *hasmatch2; @@ -2667,10 +3025,12 @@ eqjoinsel_semi(Oid opfuncoid, Oid collation, * returns NULL, though really equality functions should never do * that. */ - InitFunctionCallInfoData(*fcinfo, &eqproc, 2, collation, + InitFunctionCallInfoData(*fcinfo, &eqproc, 3, collation, NULL, NULL); fcinfo->args[0].isnull = false; fcinfo->args[1].isnull = false; + fcinfo->args[2].isnull = false; + fcinfo->args[2].value = Int32GetDatum(record_cmp_prefix); hasmatch1 = (bool *) palloc0(sslot1->nvalues * sizeof(bool)); hasmatch2 = (bool *) palloc0(clamped_nvalues2 * sizeof(bool)); @@ -3296,7 +3656,10 @@ add_unique_group_var(PlannerInfo *root, List *varinfos, } /* - * estimate_num_groups - Estimate number of groups in a grouped query + * estimate_num_groups/estimate_num_groups_incremental + * - Estimate number of groups in a grouped query. + * _incremental variant is performance optimization for + * case of adding one-by-one column * * Given a query having a GROUP BY clause, estimate how many groups there * will be --- ie, the number of distinct combinations of the GROUP BY @@ -3364,11 +3727,20 @@ double estimate_num_groups(PlannerInfo *root, List *groupExprs, double input_rows, List **pgset) { - List *varinfos = NIL; + return estimate_num_groups_incremental(root, groupExprs, + input_rows, pgset, NULL, 0); +} + +double +estimate_num_groups_incremental(PlannerInfo *root, List *groupExprs, + double input_rows, + List **pgset, List **cache_varinfos, int prevNExprs) +{ + List *varinfos = (cache_varinfos) ? *cache_varinfos : NIL; double srf_multiplier = 1.0; double numdistinct; ListCell *l; - int i; + int i, j, k; /* * We don't ever want to return an estimate of zero groups, as that tends @@ -3378,6 +3750,9 @@ estimate_num_groups(PlannerInfo *root, List *groupExprs, double input_rows, */ input_rows = clamp_row_est(input_rows); + if (input_rows == 1.0) + return 1.0; + /* * If no grouping columns, there's exactly one group. (This can't happen * for normal cases with GROUP BY or DISTINCT, but it is possible for @@ -3395,7 +3770,7 @@ estimate_num_groups(PlannerInfo *root, List *groupExprs, double input_rows, */ numdistinct = 1.0; - i = 0; + i = j = 0; foreach(l, groupExprs) { Node *groupexpr = (Node *) lfirst(l); @@ -3404,6 +3779,14 @@ estimate_num_groups(PlannerInfo *root, List *groupExprs, double input_rows, List *varshere; ListCell *l2; + /* was done on previous call */ + if (cache_varinfos && j++ < prevNExprs) + { + if (pgset) + i++; /* to keep in sync with lines below */ + continue; + } + /* is expression in this grouping set? */ if (pgset && !list_member_int(*pgset, i++)) continue; @@ -3445,6 +3828,9 @@ estimate_num_groups(PlannerInfo *root, List *groupExprs, double input_rows, } ReleaseVariableStats(vardata); + if (list_length(varinfos) > 2*list_length(groupExprs)) + continue; + /* * Else pull out the component Vars. Handle PlaceHolderVars by * recursing into their arguments (effectively assuming that the @@ -3465,13 +3851,21 @@ estimate_num_groups(PlannerInfo *root, List *groupExprs, double input_rows, if (varshere == NIL) { if (contain_volatile_functions(groupexpr)) + { + if (cache_varinfos) + *cache_varinfos = varinfos; return input_rows; + } continue; } + if (list_length(varshere) >= 8) + continue; + /* * Else add variables to varinfos list */ + k = 0; foreach(l2, varshere) { Node *var = (Node *) lfirst(l2); @@ -3479,9 +3873,15 @@ estimate_num_groups(PlannerInfo *root, List *groupExprs, double input_rows, examine_variable(root, var, 0, &vardata); varinfos = add_unique_group_var(root, varinfos, var, &vardata); ReleaseVariableStats(vardata); + + if (++k > 4) + break; } } + if (cache_varinfos) + *cache_varinfos = varinfos; + /* * If now no Vars, we must have an all-constant or all-boolean GROUP BY * list. @@ -4696,14 +5096,8 @@ get_join_variables(PlannerInfo *root, List *args, SpecialJoinInfo *sjinfo, examine_variable(root, left, 0, vardata1); examine_variable(root, right, 0, vardata2); - if (vardata1->rel && - bms_is_subset(vardata1->rel->relids, sjinfo->syn_righthand)) - *join_is_reversed = true; /* var1 is on RHS */ - else if (vardata2->rel && - bms_is_subset(vardata2->rel->relids, sjinfo->syn_lefthand)) - *join_is_reversed = true; /* var2 is on LHS */ - else - *join_is_reversed = false; + if (join_is_reversed) + *join_is_reversed = join_is_reversed_variables(sjinfo, vardata1, vardata2); } /* @@ -6062,7 +6456,7 @@ index_other_operands_eval_cost(PlannerInfo *root, List *indexquals) other_operand = NULL; /* keep compiler quiet */ } - cost_qual_eval_node(&index_qual_cost, other_operand, root); + cost_qual_eval_node_index(&index_qual_cost, other_operand, root); qual_arg_cost += index_qual_cost.startup + index_qual_cost.per_tuple; } return qual_arg_cost; diff --git a/src/backend/utils/cache/catcache.c b/src/backend/utils/cache/catcache.c index 82723055d45..ca589704b2a 100644 --- a/src/backend/utils/cache/catcache.c +++ b/src/backend/utils/cache/catcache.c @@ -21,6 +21,7 @@ #include "access/table.h" #include "access/valid.h" #include "access/xact.h" +#include "catalog/namespace.h" #include "catalog/pg_collation.h" #include "catalog/pg_operator.h" #include "catalog/pg_type.h" @@ -2008,7 +2009,7 @@ void PrepareToInvalidateCacheTuple(Relation relation, HeapTuple tuple, HeapTuple newtuple, - void (*function) (int, uint32, Oid)) + void (*function) (int, uint32, Oid, bool)) { slist_iter iter; Oid reloid; @@ -2038,6 +2039,9 @@ PrepareToInvalidateCacheTuple(Relation relation, CatCache *ccp = slist_container(CatCache, cc_next, iter.cur); uint32 hashvalue; Oid dbid; + bool isLocal = false; + Oid relationId = InvalidOid; + bool checkTemp = false; if (ccp->cc_reloid != reloid) continue; @@ -2049,7 +2053,47 @@ PrepareToInvalidateCacheTuple(Relation relation, hashvalue = CatalogCacheComputeTupleHashValue(ccp, ccp->cc_nkeys, tuple); dbid = ccp->cc_relisshared ? (Oid) 0 : MyDatabaseId; - (*function) (ccp->id, hashvalue, dbid); + if (reloid == RelationRelationId) + { + Form_pg_class classtup = (Form_pg_class) GETSTRUCT(tuple); + + isLocal = (classtup->relpersistence == RELPERSISTENCE_TEMP) ? + true : false; + } + else if (reloid == AttributeRelationId) + { + Form_pg_attribute atttup = (Form_pg_attribute) GETSTRUCT(tuple); + + relationId = atttup->attrelid; + checkTemp = true; + } + else if (reloid == IndexRelationId) + { + Form_pg_index indextup = (Form_pg_index) GETSTRUCT(tuple); + + relationId = indextup->indexrelid; + checkTemp = true; + } + + if (checkTemp) + { + HeapTuple htup = SearchSysCache1(RELOID, + ObjectIdGetDatum(relationId)); + + if (HeapTupleIsValid(htup)) + { + Form_pg_class c = (Form_pg_class)GETSTRUCT(htup); + + isLocal = (c->relisshared == false && + c->relpersistence == RELPERSISTENCE_TEMP && + isTempOrTempToastNamespace(c->relnamespace)) ? + true : false; + ReleaseSysCache(htup); + } + } + + + (*function) (ccp->id, hashvalue, dbid, isLocal); if (newtuple) { @@ -2058,7 +2102,7 @@ PrepareToInvalidateCacheTuple(Relation relation, newhashvalue = CatalogCacheComputeTupleHashValue(ccp, ccp->cc_nkeys, newtuple); if (newhashvalue != hashvalue) - (*function) (ccp->id, newhashvalue, dbid); + (*function) (ccp->id, newhashvalue, dbid, isLocal); } } } diff --git a/src/backend/utils/cache/inval.c b/src/backend/utils/cache/inval.c index 8fe1f0d9222..08631fcff74 100644 --- a/src/backend/utils/cache/inval.c +++ b/src/backend/utils/cache/inval.c @@ -115,6 +115,7 @@ #include "access/htup_details.h" #include "access/xact.h" #include "catalog/catalog.h" +#include "catalog/namespace.h" #include "catalog/pg_constraint.h" #include "miscadmin.h" #include "storage/sinval.h" @@ -351,12 +352,13 @@ AppendInvalidationMessageList(InvalidationChunk **destHdr, */ static void AddCatcacheInvalidationMessage(InvalidationListHeader *hdr, - int id, uint32 hashValue, Oid dbId) + int id, uint32 hashValue, Oid dbId, bool isLocal) { SharedInvalidationMessage msg; Assert(id < CHAR_MAX); msg.cc.id = (int8) id; + msg.cc.isLocal = (int8) isLocal; msg.cc.dbId = dbId; msg.cc.hashValue = hashValue; @@ -397,7 +399,7 @@ AddCatalogInvalidationMessage(InvalidationListHeader *hdr, */ static void AddRelcacheInvalidationMessage(InvalidationListHeader *hdr, - Oid dbId, Oid relId) + Oid dbId, Oid relId, bool isLocal) { SharedInvalidationMessage msg; @@ -414,6 +416,7 @@ AddRelcacheInvalidationMessage(InvalidationListHeader *hdr, /* OK, add the item */ msg.rc.id = SHAREDINVALRELCACHE_ID; + msg.rc.isLocal = isLocal; msg.rc.dbId = dbId; msg.rc.relId = relId; /* check AddCatcacheInvalidationMessage() for an explanation */ @@ -499,10 +502,10 @@ ProcessInvalidationMessagesMulti(InvalidationListHeader *hdr, static void RegisterCatcacheInvalidation(int cacheId, uint32 hashValue, - Oid dbId) + Oid dbId, bool isLocal) { AddCatcacheInvalidationMessage(&transInvalInfo->CurrentCmdInvalidMsgs, - cacheId, hashValue, dbId); + cacheId, hashValue, dbId, isLocal); } /* @@ -523,10 +526,10 @@ RegisterCatalogInvalidation(Oid dbId, Oid catId) * As above, but register a relcache invalidation event. */ static void -RegisterRelcacheInvalidation(Oid dbId, Oid relId) +RegisterRelcacheInvalidation(Oid dbId, Oid relId, bool tempRel) { AddRelcacheInvalidationMessage(&transInvalInfo->CurrentCmdInvalidMsgs, - dbId, relId); + dbId, relId, tempRel); /* * Most of the time, relcache invalidation is associated with system @@ -1138,6 +1141,8 @@ CacheInvalidateHeapTuple(Relation relation, Oid tupleRelId; Oid databaseId; Oid relationId; + bool tempRel = false; + bool checkTemp = false; /* Do nothing during bootstrap */ if (IsBootstrapProcessingMode()) @@ -1193,6 +1198,9 @@ CacheInvalidateHeapTuple(Relation relation, databaseId = InvalidOid; else databaseId = MyDatabaseId; + + tempRel = (classtup->relpersistence == RELPERSISTENCE_TEMP) ? + true : false; } else if (tupleRelId == AttributeRelationId) { @@ -1211,6 +1219,7 @@ CacheInvalidateHeapTuple(Relation relation, * never come here for a shared rel anyway.) */ databaseId = MyDatabaseId; + checkTemp = true; } else if (tupleRelId == IndexRelationId) { @@ -1224,6 +1233,7 @@ CacheInvalidateHeapTuple(Relation relation, */ relationId = indextup->indexrelid; databaseId = MyDatabaseId; + checkTemp = true; } else if (tupleRelId == ConstraintRelationId) { @@ -1245,10 +1255,27 @@ CacheInvalidateHeapTuple(Relation relation, else return; + if (checkTemp) + { + HeapTuple htup = SearchSysCache1(RELOID, ObjectIdGetDatum(relationId)); + + if (HeapTupleIsValid(htup)) + { + Form_pg_class c = (Form_pg_class)GETSTRUCT(htup); + + tempRel = (c->relisshared == false && + c->relpersistence == RELPERSISTENCE_TEMP && + isTempOrTempToastNamespace(c->relnamespace)) ? + true : false; + + ReleaseSysCache(htup); + } + } + /* * Yes. We need to register a relcache invalidation event. */ - RegisterRelcacheInvalidation(databaseId, relationId); + RegisterRelcacheInvalidation(databaseId, relationId, tempRel); } /* @@ -1300,7 +1327,10 @@ CacheInvalidateRelcache(Relation relation) else databaseId = MyDatabaseId; - RegisterRelcacheInvalidation(databaseId, relationId); + RegisterRelcacheInvalidation(databaseId, relationId, + relation->rd_rel->relisshared == false && + RELATION_IS_LOCAL(relation) && + !RELATION_IS_OTHER_TEMP(relation)); } /* @@ -1315,7 +1345,7 @@ CacheInvalidateRelcacheAll(void) { PrepareInvalidationState(); - RegisterRelcacheInvalidation(InvalidOid, InvalidOid); + RegisterRelcacheInvalidation(InvalidOid, InvalidOid, false); } /* @@ -1336,7 +1366,10 @@ CacheInvalidateRelcacheByTuple(HeapTuple classTuple) databaseId = InvalidOid; else databaseId = MyDatabaseId; - RegisterRelcacheInvalidation(databaseId, relationId); + RegisterRelcacheInvalidation(databaseId, relationId, + classtup->relisshared == false && + classtup->relpersistence == RELPERSISTENCE_TEMP && + isTempOrTempToastNamespace(classtup->relnamespace)); } /* diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c index 3303c31e575..1f6eb7736f0 100644 --- a/src/backend/utils/cache/lsyscache.c +++ b/src/backend/utils/cache/lsyscache.c @@ -43,6 +43,7 @@ #include "utils/datum.h" #include "utils/fmgroids.h" #include "utils/lsyscache.h" +#include "utils/memutils.h" #include "utils/rel.h" #include "utils/syscache.h" #include "utils/typcache.h" @@ -3188,6 +3189,52 @@ get_attstatsslot(AttStatsSlot *sslot, HeapTuple statstuple, return true; } +AttStatsSlot* +fill_attstatsslot(AttStatsSlot *sslots, HeapTuple statstuple, + int reqkind, Oid reqop, int flags) +{ + int add_flags = 0, has_flags = 0; + AttStatsSlot *sslot; + MemoryContext oldctx; + + if (reqkind >= STATISTIC_NUM_SLOTS) + return NULL; /* not there */ + + sslot = sslots + reqkind; + + if (sslot->values != NULL) + has_flags |= ATTSTATSSLOT_VALUES; + if (sslot->numbers != NULL) + has_flags |= ATTSTATSSLOT_NUMBERS; + + if ((flags & ATTSTATSSLOT_VALUES) && !(has_flags & ATTSTATSSLOT_VALUES)) + add_flags |= ATTSTATSSLOT_VALUES; + + if ((flags & ATTSTATSSLOT_NUMBERS) && !(has_flags & ATTSTATSSLOT_NUMBERS)) + add_flags |= ATTSTATSSLOT_NUMBERS; + + if (add_flags == 0 && (reqop == InvalidOid || sslot->staop == reqop)) + return sslot; + + sslot->incache = false; + free_attstatsslot(sslot); + + oldctx = MemoryContextSwitchTo(GetMemoryChunkContext(sslots)); + + if (get_attstatsslot(sslot, statstuple, reqkind, reqop, + add_flags | has_flags)) + { + sslot->incache = true; + MemoryContextSwitchTo(oldctx); + return sslot; + } + else + { + MemoryContextSwitchTo(oldctx); + return NULL; + } +} + /* * free_attstatsslot * Free data allocated by get_attstatsslot @@ -3195,6 +3242,10 @@ get_attstatsslot(AttStatsSlot *sslot, HeapTuple statstuple, void free_attstatsslot(AttStatsSlot *sslot) { + /* do not free cached slot */ + if (sslot->incache) + return; + /* The values[] array was separately palloc'd by deconstruct_array */ if (sslot->values) pfree(sslot->values); diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c index 09ac55a3cc9..143f0ecaf83 100644 --- a/src/backend/utils/cache/typcache.c +++ b/src/backend/utils/cache/typcache.c @@ -77,6 +77,13 @@ /* The main type cache hashtable searched by lookup_type_cache */ static HTAB *TypeCacheHash = NULL; +typedef struct mapRelTypeEntry +{ + Oid typrelid; + Oid type_id; +} mapRelTypeEntry; +static HTAB *mapRelType = NULL; + /* List of type cache entries for domain types */ static TypeCacheEntry *firstDomainTypeEntry = NULL; @@ -320,6 +327,15 @@ static TupleDesc find_or_make_matching_shared_tupledesc(TupleDesc tupdesc); static dsa_pointer share_tupledesc(dsa_area *area, TupleDesc tupdesc, uint32 typmod); +/* + * Hashing function should compatible with syscache hashing function to use + * hash_seq_init_with_hash_value() + */ +static uint32 +type_cache_hash(const void *key, Size keysize) +{ + return GetSysCacheHashValue1(TYPEOID, ObjectIdGetDatum(*(const Oid*)key)); +} /* * lookup_type_cache @@ -346,8 +362,14 @@ lookup_type_cache(Oid type_id, int flags) MemSet(&ctl, 0, sizeof(ctl)); ctl.keysize = sizeof(Oid); ctl.entrysize = sizeof(TypeCacheEntry); + ctl.hash = type_cache_hash; TypeCacheHash = hash_create("Type information cache", 64, - &ctl, HASH_ELEM | HASH_BLOBS); + &ctl, HASH_ELEM | HASH_FUNCTION); + + ctl.keysize = sizeof(Oid); + ctl.entrysize = sizeof(mapRelTypeEntry); + mapRelType = hash_create("Map reloid to typeoid", 64, + &ctl, HASH_ELEM | HASH_BLOBS); /* Also set up callbacks for SI invalidations */ CacheRegisterRelcacheCallback(TypeCacheRelCallback, (Datum) 0); @@ -398,8 +420,7 @@ lookup_type_cache(Oid type_id, int flags) /* These fields can never change, by definition */ typentry->type_id = type_id; - typentry->type_id_hash = GetSysCacheHashValue1(TYPEOID, - ObjectIdGetDatum(type_id)); + typentry->type_id_hash = get_hash_value(TypeCacheHash, &type_id); /* Keep this part in sync with the code below */ typentry->typlen = typtup->typlen; @@ -419,6 +440,18 @@ lookup_type_cache(Oid type_id, int flags) firstDomainTypeEntry = typentry; } + if (OidIsValid(typtup->typrelid)) + { + mapRelTypeEntry *relentry; + + relentry = (mapRelTypeEntry*) hash_search(mapRelType, + &typentry->typrelid, + HASH_ENTER, NULL); + + relentry->typrelid = typentry->typrelid; + relentry->type_id = typentry->type_id; + } + ReleaseSysCache(tp); } else if (!(typentry->flags & TCFLAGS_HAVE_PG_TYPE_DATA)) @@ -456,6 +489,18 @@ lookup_type_cache(Oid type_id, int flags) typentry->typcollation = typtup->typcollation; typentry->flags |= TCFLAGS_HAVE_PG_TYPE_DATA; + if (OidIsValid(typtup->typrelid)) + { + mapRelTypeEntry *relentry; + + relentry = (mapRelTypeEntry*) hash_search(mapRelType, + &typentry->typrelid, + HASH_ENTER, NULL); + + relentry->typrelid = typentry->typrelid; + relentry->type_id = typentry->type_id; + } + ReleaseSysCache(tp); } @@ -2167,58 +2212,72 @@ SharedRecordTypmodRegistryAttach(SharedRecordTypmodRegistry *registry) static void TypeCacheRelCallback(Datum arg, Oid relid) { - HASH_SEQ_STATUS status; TypeCacheEntry *typentry; /* TypeCacheHash must exist, else this callback wouldn't be registered */ - hash_seq_init(&status, TypeCacheHash); - while ((typentry = (TypeCacheEntry *) hash_seq_search(&status)) != NULL) + + if (OidIsValid(relid)) { - if (typentry->typtype == TYPTYPE_COMPOSITE) + mapRelTypeEntry *relentry; + + relentry = (mapRelTypeEntry *) hash_search(mapRelType, + &relid, + HASH_FIND, NULL); + + if (relentry != NULL) { - /* Skip if no match, unless we're zapping all composite types */ - if (relid != typentry->typrelid && relid != InvalidOid) - continue; + typentry = (TypeCacheEntry *) hash_search(TypeCacheHash, + &relentry->type_id, + HASH_FIND, NULL); - /* Delete tupdesc if we have it */ - if (typentry->tupDesc != NULL) + if (typentry != NULL) { - /* - * Release our refcount, and free the tupdesc if none remain. - * (Can't use DecrTupleDescRefCount because this reference is - * not logged in current resource owner.) - */ - Assert(typentry->tupDesc->tdrefcount > 0); - if (--typentry->tupDesc->tdrefcount == 0) - FreeTupleDesc(typentry->tupDesc); - typentry->tupDesc = NULL; - - /* - * Also clear tupDesc_identifier, so that anything watching - * that will realize that the tupdesc has possibly changed. - * (Alternatively, we could specify that to detect possible - * tupdesc change, one must check for tupDesc != NULL as well - * as tupDesc_identifier being the same as what was previously - * seen. That seems error-prone.) - */ - typentry->tupDesc_identifier = 0; - } + Assert(typentry->typtype == TYPTYPE_COMPOSITE); + Assert(relid == typentry->typrelid); - /* Reset equality/comparison/hashing validity information */ - typentry->flags &= ~TCFLAGS_OPERATOR_FLAGS; - } - else if (typentry->typtype == TYPTYPE_DOMAIN) - { - /* - * If it's domain over composite, reset flags. (We don't bother - * trying to determine whether the specific base type needs a - * reset.) Note that if we haven't determined whether the base - * type is composite, we don't need to reset anything. - */ - if (typentry->flags & TCFLAGS_DOMAIN_BASE_IS_COMPOSITE) + /* Delete tupdesc if we have it */ + if (typentry->tupDesc != NULL) + { + /* + * Release our refcount, and free the tupdesc if none remain. + * (Can't use DecrTupleDescRefCount because this reference is + * not logged in current resource owner.) + */ + Assert(typentry->tupDesc->tdrefcount > 0); + if (--typentry->tupDesc->tdrefcount == 0) + FreeTupleDesc(typentry->tupDesc); + typentry->tupDesc = NULL; + + /* + * Also clear tupDesc_identifier, so that anything watching + * that will realize that the tupdesc has possibly changed. + * (Alternatively, we could specify that to detect possible + * tupdesc change, one must check for tupDesc != NULL as well + * as tupDesc_identifier being the same as what was previously + * seen. That seems error-prone.) + */ + typentry->tupDesc_identifier = 0; + } + + /* Reset equality/comparison/hashing validity information */ typentry->flags &= ~TCFLAGS_OPERATOR_FLAGS; + } } } + + for (typentry = firstDomainTypeEntry; + typentry != NULL; + typentry = typentry->nextDomain) + { + /* + * If it's domain over composite, reset flags. (We don't bother + * trying to determine whether the specific base type needs a + * reset.) Note that if we haven't determined whether the base + * type is composite, we don't need to reset anything. + */ + if (typentry->flags & TCFLAGS_DOMAIN_BASE_IS_COMPOSITE) + typentry->flags &= ~TCFLAGS_OPERATOR_FLAGS; + } } /* @@ -2236,20 +2295,20 @@ TypeCacheTypCallback(Datum arg, int cacheid, uint32 hashvalue) TypeCacheEntry *typentry; /* TypeCacheHash must exist, else this callback wouldn't be registered */ - hash_seq_init(&status, TypeCacheHash); + if (hashvalue == 0) + hash_seq_init(&status, TypeCacheHash); + else + hash_seq_init_with_hash_value(&status, TypeCacheHash, hashvalue); + while ((typentry = (TypeCacheEntry *) hash_seq_search(&status)) != NULL) { - /* Is this the targeted type row (or it's a total cache flush)? */ - if (hashvalue == 0 || typentry->type_id_hash == hashvalue) - { - /* - * Mark the data obtained directly from pg_type as invalid. Also, - * if it's a domain, typnotnull might've changed, so we'll need to - * recalculate its constraints. - */ - typentry->flags &= ~(TCFLAGS_HAVE_PG_TYPE_DATA | - TCFLAGS_CHECKED_DOMAIN_CONSTRAINTS); - } + /* + * Mark the data obtained directly from pg_type as invalid. Also, + * if it's a domain, typnotnull might've changed, so we'll need to + * recalculate its constraints. + */ + typentry->flags &= ~(TCFLAGS_HAVE_PG_TYPE_DATA | + TCFLAGS_CHECKED_DOMAIN_CONSTRAINTS); } } diff --git a/src/backend/utils/hash/dynahash.c b/src/backend/utils/hash/dynahash.c index 2688e277267..5af23c5b6fa 100644 --- a/src/backend/utils/hash/dynahash.c +++ b/src/backend/utils/hash/dynahash.c @@ -1383,10 +1383,37 @@ hash_seq_init(HASH_SEQ_STATUS *status, HTAB *hashp) status->hashp = hashp; status->curBucket = 0; status->curEntry = NULL; + status->hasHashVal = false; if (!hashp->frozen) register_seq_scan(hashp); } +void +hash_seq_init_with_hash_value(HASH_SEQ_STATUS *status, HTAB *hashp, + uint32 hashvalue) +{ + HASHHDR *hctl = hashp->hctl; + long segment_num; + long segment_ndx; + HASHSEGMENT segp; + + hash_seq_init(status, hashp); + status->hasHashVal = true; + status->hashvalue = hashvalue; + + status->curBucket = calc_bucket(hctl, hashvalue); + + segment_num = status->curBucket >> hashp->sshift; + segment_ndx = MOD(status->curBucket, hashp->ssize); + + segp = hashp->dir[segment_num]; + + if (segp == NULL) + hash_corrupted(hashp); + + status->curEntry = segp[segment_ndx]; +} + void * hash_seq_search(HASH_SEQ_STATUS *status) { @@ -1400,6 +1427,20 @@ hash_seq_search(HASH_SEQ_STATUS *status) uint32 curBucket; HASHELEMENT *curElem; + if (status->hasHashVal) + { + while ((curElem = status->curEntry) != NULL) + { + status->curEntry = curElem->link; + if (status->hashvalue != curElem->hashvalue) + continue; + return (void *) ELEMENTKEY(curElem); + } + + hash_seq_term(status); + return NULL; + } + if ((curElem = status->curEntry) != NULL) { /* Continuing scan of curBucket... */ diff --git a/src/backend/utils/init/globals.c b/src/backend/utils/init/globals.c index 9793354a02f..eda120fddab 100644 --- a/src/backend/utils/init/globals.c +++ b/src/backend/utils/init/globals.c @@ -30,6 +30,7 @@ ProtocolVersion FrontendProtocol; volatile sig_atomic_t InterruptPending = false; volatile sig_atomic_t QueryCancelPending = false; volatile sig_atomic_t ProcDiePending = false; +volatile sig_atomic_t CheckClientConnectionPending = false; volatile sig_atomic_t ClientConnectionLost = false; volatile sig_atomic_t IdleInTransactionSessionTimeoutPending = false; volatile sig_atomic_t ProcSignalBarrierPending = false; diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c index 97e2615773c..2b502856260 100644 --- a/src/backend/utils/init/postinit.c +++ b/src/backend/utils/init/postinit.c @@ -73,6 +73,7 @@ static void ShutdownPostgres(int code, Datum arg); static void StatementTimeoutHandler(void); static void LockTimeoutHandler(void); static void IdleInTransactionSessionTimeoutHandler(void); +static void ClientCheckTimeoutHandler(void); static bool ThereIsAtLeastOneRole(void); static void process_startup_options(Port *port, bool am_superuser); static void process_settings(Oid databaseid, Oid roleid); @@ -620,6 +621,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); } /* @@ -1240,6 +1242,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 d52aa9ae0e8..063e9c5594e 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -128,6 +128,7 @@ extern char *temp_tablespaces; extern bool ignore_checksum_failure; extern bool ignore_invalid_pages; extern bool synchronize_seqscans; +extern bool enable_self_join_removal; #ifdef TRACE_SYNCSCAN extern bool trace_syncscan; @@ -222,6 +223,7 @@ static bool check_recovery_target_lsn(char **newval, void **extra, GucSource sou static void assign_recovery_target_lsn(const char *newval, void *extra); static bool check_primary_slot_name(char **newval, void **extra, GucSource source); static bool check_default_with_oids(bool *newval, void **extra, GucSource source); +static void assign_default_with_oids(bool newval, void *extra); /* Private functions in guc-file.l that need to be called from guc.c */ static ConfigVariable *ProcessConfigFileInternal(GucContext context, @@ -1113,6 +1115,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."), @@ -1691,7 +1703,7 @@ static struct config_bool ConfigureNamesBool[] = }, &default_with_oids, false, - check_default_with_oids, NULL, NULL + check_default_with_oids, assign_default_with_oids, NULL }, { {"logging_collector", PGC_POSTMASTER, LOGGING_WHERE, @@ -2022,7 +2034,6 @@ static struct config_bool ConfigureNamesBool[] = */ NULL, NULL, NULL }, - { {"jit_tuple_deforming", PGC_USERSET, DEVELOPER_OPTIONS, gettext_noop("Allow JIT compilation of tuple deforming."), @@ -12082,13 +12093,19 @@ check_default_with_oids(bool *newval, void **extra, GucSource source) if (*newval) { /* check the GUC's definition for an explanation */ - GUC_check_errcode(ERRCODE_FEATURE_NOT_SUPPORTED); - GUC_check_errmsg("tables declared WITH OIDS are not supported"); + ereport(WARNING, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("tables declared WITH OIDS are not supported, ignored"))); - return false; + *newval = false; } return true; } +static void +assign_default_with_oids(bool newval, void *extra) +{ +} + #include "guc-file.c" diff --git a/src/backend/utils/mmgr/aset.c b/src/backend/utils/mmgr/aset.c index 3c70a7b5f24..468dfab50c8 100644 --- a/src/backend/utils/mmgr/aset.c +++ b/src/backend/utils/mmgr/aset.c @@ -1472,7 +1472,6 @@ AllocSetCheck(MemoryContext context) chsize = chunk->size; /* aligned chunk size */ dsize = chunk->requested_size; /* real data */ - /* * Check chunk size */ diff --git a/src/backend/utils/sort/tuplestore.c b/src/backend/utils/sort/tuplestore.c index 452a85a423b..6def5175fbe 100644 --- a/src/backend/utils/sort/tuplestore.c +++ b/src/backend/utils/sort/tuplestore.c @@ -545,7 +545,7 @@ tuplestore_select_read_pointer(Tuplestorestate *state, int ptr) int64 tuplestore_tuple_count(Tuplestorestate *state) { - return state->tuples; + return (state) ? state->tuples : 0; } /* diff --git a/src/bin/pg_basebackup/pg_receivewal.c b/src/bin/pg_basebackup/pg_receivewal.c index 854990081f6..ae1d3c3d5c9 100644 --- a/src/bin/pg_basebackup/pg_receivewal.c +++ b/src/bin/pg_basebackup/pg_receivewal.c @@ -96,6 +96,7 @@ usage(void) printf(_(" -d, --dbname=CONNSTR connection string\n")); printf(_(" -h, --host=HOSTNAME database server host or socket directory\n")); printf(_(" -p, --port=PORT database server port number\n")); + printf(_(" -u, --umask set files mode according to umask (might break security!)\n")); printf(_(" -U, --username=NAME connect as specified database user\n")); printf(_(" -w, --no-password never prompt for password\n")); printf(_(" -W, --password force password prompt (should happen automatically)\n")); @@ -475,6 +476,7 @@ main(int argc, char **argv) {"endpos", required_argument, NULL, 'E'}, {"host", required_argument, NULL, 'h'}, {"port", required_argument, NULL, 'p'}, + {"umask", no_argument, NULL, 'u'}, {"username", required_argument, NULL, 'U'}, {"no-loop", no_argument, NULL, 'n'}, {"no-password", no_argument, NULL, 'w'}, @@ -517,7 +519,7 @@ main(int argc, char **argv) } } - while ((c = getopt_long(argc, argv, "D:d:E:h:p:U:s:S:nwWvZ:", + while ((c = getopt_long(argc, argv, "D:d:E:h:p:U:s:S:nuwWvZ:", long_options, &option_index)) != -1) { switch (c) @@ -539,6 +541,9 @@ main(int argc, char **argv) } dbport = pg_strdup(optarg); break; + case 'u': + useumask = 1; + break; case 'U': dbuser = pg_strdup(optarg); break; diff --git a/src/bin/pg_basebackup/pg_recvlogical.c b/src/bin/pg_basebackup/pg_recvlogical.c index f049a2480ed..3b2b7961ccb 100644 --- a/src/bin/pg_basebackup/pg_recvlogical.c +++ b/src/bin/pg_basebackup/pg_recvlogical.c @@ -324,11 +324,14 @@ StreamLogicalLog(void) { struct stat statbuf; + mode_t mode = (useumask == 1) ? + (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) : (S_IRUSR | S_IWUSR); + if (strcmp(outfile, "-") == 0) outfd = fileno(stdout); else outfd = open(outfile, O_CREAT | O_APPEND | O_WRONLY | PG_BINARY, - S_IRUSR | S_IWUSR); + mode); if (outfd == -1) { pg_log_error("could not open log file \"%s\": %m", outfile); diff --git a/src/bin/pg_basebackup/streamutil.c b/src/bin/pg_basebackup/streamutil.c index e2033b7e377..8cbc0d087ce 100644 --- a/src/bin/pg_basebackup/streamutil.c +++ b/src/bin/pg_basebackup/streamutil.c @@ -48,6 +48,7 @@ char *dbhost = NULL; char *dbuser = NULL; char *dbport = NULL; char *dbname = NULL; +int useumask = 0; /* 0=auto, -1=never, 1=always */ int dbgetpassword = 0; /* 0=auto, -1=never, 1=always */ static bool have_password = false; static char password[100]; diff --git a/src/bin/pg_basebackup/streamutil.h b/src/bin/pg_basebackup/streamutil.h index 57448656e3d..eaed4cedb3f 100644 --- a/src/bin/pg_basebackup/streamutil.h +++ b/src/bin/pg_basebackup/streamutil.h @@ -22,6 +22,7 @@ extern char *dbhost; extern char *dbuser; extern char *dbport; extern char *dbname; +extern int useumask; extern int dbgetpassword; extern uint32 WalSegSz; diff --git a/src/bin/pg_basebackup/walmethods.c b/src/bin/pg_basebackup/walmethods.c index ee5193bcb42..707007f938b 100644 --- a/src/bin/pg_basebackup/walmethods.c +++ b/src/bin/pg_basebackup/walmethods.c @@ -99,6 +99,8 @@ dir_open_for_write(const char *pathname, const char *temp_suffix, size_t pad_to_ #ifdef HAVE_LIBZ gzFile gzfp = NULL; #endif + mode_t mode = (useumask == 1) ? + (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) : (S_IRUSR | S_IWUSR); dir_clear_error(); @@ -113,7 +115,7 @@ dir_open_for_write(const char *pathname, const char *temp_suffix, size_t pad_to_ * does not do any system calls to fsync() to make changes permanent on * disk. */ - fd = open(tmppath, O_WRONLY | O_CREAT | PG_BINARY, pg_file_create_mode); + fd = open(tmppath, O_WRONLY | O_CREAT | PG_BINARY, pg_file_create_mode | mode); if (fd < 0) { dir_data->lasterrno = errno; @@ -630,6 +632,8 @@ tar_get_file_name(const char *pathname, const char *temp_suffix) static Walfile tar_open_for_write(const char *pathname, const char *temp_suffix, size_t pad_to_size) { + mode_t mode = (useumask == 1) ? + (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) : (S_IRUSR | S_IWUSR); char *tmppath; tar_clear_error(); @@ -641,7 +645,7 @@ tar_open_for_write(const char *pathname, const char *temp_suffix, size_t pad_to_ */ tar_data->fd = open(tar_data->tarfilename, O_WRONLY | O_CREAT | PG_BINARY, - pg_file_create_mode); + pg_file_create_mode | mode); if (tar_data->fd < 0) { tar_data->lasterrno = errno; diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c index c68b86bc766..30e0aced1b4 100644 --- a/src/bin/pg_dump/common.c +++ b/src/bin/pg_dump/common.c @@ -52,6 +52,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; @@ -60,6 +61,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 */ @@ -77,9 +79,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 @@ -97,6 +98,7 @@ getSchemaData(Archive *fout, int *numTablesPtr) ExtensionInfo *extinfo; PublicationInfo *pubinfo; InhInfo *inhinfo; + IndxInfo *idxinfo; int numAggregates; int numInherits; int numRules; @@ -232,7 +234,8 @@ getSchemaData(Archive *fout, int *numTablesPtr) getPartitioningInfo(fout); pg_log_info("reading indexes"); - getIndexes(fout, tblinfo, numTables); + idxinfo = getIndexes(fout, tblinfo, numTables, &numIndexes); + idxinfoindex = buildIndexArray(idxinfo, numIndexes, sizeof(IndxInfo)); pg_log_info("flagging indexes in partitioned tables"); flagInhIndexes(fout, tblinfo, numTables); @@ -380,7 +383,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) @@ -936,15 +939,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 7ac6a75b780..618ec9ff4fd 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -1635,11 +1635,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; @@ -4532,6 +4543,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", @@ -7123,8 +7137,8 @@ getPartitioningInfo(Archive *fout) * Note: index data is not returned directly to the caller, but it * does get entered into the DumpableObject tables. */ -void -getIndexes(Archive *fout, TableInfo tblinfo[], int numTables) +IndxInfo * +getIndexes(Archive *fout, TableInfo tblinfo[], int numTables, int *numIndexes) { int i, j; @@ -7154,6 +7168,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++) { @@ -7397,6 +7414,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++) { @@ -7469,6 +7487,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; } /* @@ -16877,8 +16916,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); @@ -17128,8 +17172,14 @@ dumpConstraint(Archive *fout, ConstraintInfo *coninfo) coninfo->dobj.name); if (dopt->binary_upgrade) + { + if (indxinfo->indnkeyattrs > 1) + binary_upgrade_set_type_oids_by_rel_oid(fout, q, + indxinfo->dobj.catId.oid); + binary_upgrade_set_pg_class_oids(fout, q, indxinfo->dobj.catId.oid, true); + } appendPQExpBuffer(q, "ALTER %sTABLE ONLY %s\n", foreign, fmtQualifiedDumpable(tbinfo)); @@ -18703,6 +18753,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: @@ -18719,7 +18790,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 43095c25ddc..067dae760ea 100644 --- a/src/bin/pg_dump/pg_dump.h +++ b/src/bin/pg_dump/pg_dump.h @@ -664,6 +664,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); @@ -693,7 +694,8 @@ extern TableInfo *getTables(Archive *fout, int *numTables); extern void getOwnedSeqs(Archive *fout, TableInfo tblinfo[], int numTables); extern InhInfo *getInherits(Archive *fout, int *numInherits); extern void getPartitioningInfo(Archive *fout); -extern void getIndexes(Archive *fout, TableInfo tblinfo[], int numTables); +extern IndxInfo *getIndexes(Archive *fout, TableInfo tblinfo[], int numTables, + int *numIndexes); extern void getExtendedStatistics(Archive *fout); extern void getConstraints(Archive *fout, TableInfo tblinfo[], int numTables); extern RuleInfo *getRules(Archive *fout, int *numRules); diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h index f58e8675f32..c40e6b11029 100644 --- a/src/include/catalog/index.h +++ b/src/include/catalog/index.h @@ -51,6 +51,7 @@ extern void index_check_primary_key(Relation heapRel, #define INDEX_CREATE_IF_NOT_EXISTS (1 << 4) #define INDEX_CREATE_PARTITIONED (1 << 5) #define INDEX_CREATE_INVALID (1 << 6) +#define INDEX_CREATE_WITHOUT_TYPE (1 << 7) extern Oid index_create(Relation heapRelation, const char *indexRelationName, diff --git a/src/include/catalog/pg_type.dat b/src/include/catalog/pg_type.dat index e8be0008353..6bf8a57977f 100644 --- a/src/include/catalog/pg_type.dat +++ b/src/include/catalog/pg_type.dat @@ -630,4 +630,12 @@ typoutput => 'anycompatiblerange_out', typreceive => '-', typsend => '-', typalign => 'd', typstorage => 'x' }, +{ oid => '14756', descr => 'pseudo-type representing removed abstime', + typname => 'abstime', typlen => '-1', typbyval => 'f', typtype => 'p', + typcategory => 'P', typinput => '-', typoutput => '-', + typreceive => '-', typsend => '-', typalign => 'c' }, +{ oid => '14757', descr => 'pseudo-type representing removed reltime', + typname => 'reltime', typlen => '-1', typbyval => 'f', typtype => 'p', + typcategory => 'P', typinput => '-', typoutput => '-', + typreceive => '-', typsend => '-', typalign => 'c' }, ] diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h index afbab95adcd..1d62db38054 100644 --- a/src/include/executor/execExpr.h +++ b/src/include/executor/execExpr.h @@ -345,12 +345,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; @@ -608,6 +613,7 @@ typedef struct ExprEvalStep { /* out-of-line state, created by nodeSubplan.c */ AlternativeSubPlanState *asstate; + bool *guaranteed_empty; } alternative_subplan; /* for EEOP_AGG_*DESERIALIZE */ diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h index 54c5fa77977..15b76aa2ab6 100644 --- a/src/include/libpq/libpq.h +++ b/src/include/libpq/libpq.h @@ -74,6 +74,7 @@ extern int pq_peekbyte(void); extern int pq_getbyte_if_available(unsigned char *c); extern bool pq_buffer_has_data(void); extern int pq_putbytes(const char *s, size_t len); +extern void pq_check_client_connection(void); /* * prototypes for functions in be-secure.c diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h index 6b2b4343a0d..abd2aa06425 100644 --- a/src/include/miscadmin.h +++ b/src/include/miscadmin.h @@ -93,6 +93,7 @@ extern PGDLLIMPORT volatile sig_atomic_t ProcDiePending; extern PGDLLIMPORT volatile sig_atomic_t IdleInTransactionSessionTimeoutPending; extern PGDLLIMPORT volatile sig_atomic_t ProcSignalBarrierPending; +extern PGDLLIMPORT volatile sig_atomic_t CheckClientConnectionPending; extern PGDLLIMPORT volatile sig_atomic_t ClientConnectionLost; /* these are marked volatile because they are examined by signal handlers: */ diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index caf0d41f0bb..c9ced8b535e 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -113,6 +113,8 @@ typedef struct ExprState Datum *innermost_domainval; bool *innermost_domainnull; + + bool guaranteed_empty; } ExprState; @@ -914,6 +916,7 @@ typedef struct AlternativeSubPlanState AlternativeSubPlan *subplan; /* expression plan node */ List *subplans; /* SubPlanStates of alternative subplans */ int active; /* list index of the one we're using */ + bool guaranteed_empty; } AlternativeSubPlanState; /* @@ -1017,6 +1020,8 @@ typedef struct PlanState */ TupleDesc scandesc; + bool guaranteed_empty; + /* * Define the slot types for inner, outer and scanslots for expression * contexts with this state as a parent. If *opsset is set, then diff --git a/src/include/nodes/nodeFuncs.h b/src/include/nodes/nodeFuncs.h index 9cc56eecaa3..d5616d4cb2f 100644 --- a/src/include/nodes/nodeFuncs.h +++ b/src/include/nodes/nodeFuncs.h @@ -28,6 +28,7 @@ * contents */ #define QTW_DONT_COPY_QUERY 0x40 /* do not copy top Query */ #define QTW_EXAMINE_SORTGROUP 0x80 /* include SortGroupNode lists */ +#define QTW_DONT_COPY_DEFAULT 0x00 /* only custom mutator will copy */ /* callback function for check_functions_in_node */ typedef bool (*check_function_callback) (Oid func_id, void *context); @@ -132,7 +133,7 @@ extern bool check_functions_in_node(Node *node, check_function_callback checker, extern bool expression_tree_walker(Node *node, bool (*walker) (), void *context); extern Node *expression_tree_mutator(Node *node, Node *(*mutator) (), - void *context); + void *context, int flags); extern bool query_tree_walker(Query *query, bool (*walker) (), void *context, int flags); diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index d2b4271de9d..1e851837c1c 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -15,10 +15,12 @@ #define PATHNODES_H #include "access/sdir.h" +#include "catalog/pg_statistic.h" #include "lib/stringinfo.h" #include "nodes/params.h" #include "nodes/parsenodes.h" #include "storage/block.h" +#include "utils/lsyscache.h" /* @@ -868,6 +870,10 @@ struct IndexOptInfo bool amcanmarkpos; /* does AM support mark/restore? */ /* Rather than include amapi.h here, we declare amcostestimate like this */ void (*amcostestimate) (); /* AM's cost estimator */ + + /* cache for per-tuple index statistic. That stats could be large and it + * will be expensive to uncompress it every time */ + AttStatsSlot *sslots; }; /* @@ -1410,6 +1416,12 @@ typedef struct AppendPath /* Index of first partial path in subpaths; list_length(subpaths) if none */ int first_partial_path; double limit_tuples; /* hard limit on output tuples, or -1 */ + bool pull_tlist; /* if = true, create_append_plan() + * should get targetlist from any + * subpath - they are the same, + * because the only place - append + * index scan for range OR */ + } AppendPath; #define IS_DUMMY_APPEND(p) \ diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h index 90f02ce6fdd..531da7d5b15 100644 --- a/src/include/nodes/plannodes.h +++ b/src/include/nodes/plannodes.h @@ -20,6 +20,7 @@ #include "nodes/bitmapset.h" #include "nodes/lockoptions.h" #include "nodes/primnodes.h" +#include "portability/instr_time.h" /* ---------------------------------------------------------------- @@ -91,6 +92,8 @@ typedef struct PlannedStmt Node *utilityStmt; /* non-null if this is utility stmt */ + instr_time planDuration; /* time spent on planning */ + /* statement location in source string (copied from Query) */ int stmt_location; /* start location, or -1 if unknown */ int stmt_len; /* length in bytes; 0 means "rest of string" */ diff --git a/src/include/nodes/supportnodes.h b/src/include/nodes/supportnodes.h index 9a33c4c896f..e93e7b15bc0 100644 --- a/src/include/nodes/supportnodes.h +++ b/src/include/nodes/supportnodes.h @@ -34,6 +34,20 @@ #define SUPPORTNODES_H #include "nodes/primnodes.h" +typedef enum +{ + Pattern_Type_Like, + Pattern_Type_Like_IC, + Pattern_Type_Regex, + Pattern_Type_Regex_IC, + Pattern_Type_Prefix +} Pattern_Type; + +typedef enum +{ + Pattern_Prefix_None, Pattern_Prefix_Partial, Pattern_Prefix_Exact +} Pattern_Prefix_Status; + struct PlannerInfo; /* avoid including pathnodes.h here */ struct IndexOptInfo; diff --git a/src/include/optimizer/cost.h b/src/include/optimizer/cost.h index 6141654e478..403023d6751 100644 --- a/src/include/optimizer/cost.h +++ b/src/include/optimizer/cost.h @@ -107,7 +107,9 @@ extern void cost_incremental_sort(Path *path, Cost input_startup_cost, Cost input_total_cost, double input_tuples, int width, Cost comparison_cost, int sort_mem, double limit_tuples); -extern void cost_append(AppendPath *path); +extern Cost cost_sort_estimate(PlannerInfo *root, List *pathkeys, + int nPresortedKeys, double tuples); +extern void cost_append(AppendPath *path, PlannerInfo *root); extern void cost_merge_append(Path *path, PlannerInfo *root, List *pathkeys, int n_streams, Cost input_startup_cost, Cost input_total_cost, @@ -167,6 +169,7 @@ extern void cost_gather_merge(GatherMergePath *path, PlannerInfo *root, extern void cost_subplan(PlannerInfo *root, SubPlan *subplan, Plan *plan); extern void cost_qual_eval(QualCost *cost, List *quals, PlannerInfo *root); extern void cost_qual_eval_node(QualCost *cost, Node *qual, PlannerInfo *root); +extern void cost_qual_eval_node_index(QualCost *cost, Node *qual, PlannerInfo *root); extern void compute_semi_anti_join_factors(PlannerInfo *root, RelOptInfo *joinrel, RelOptInfo *outerrel, diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h index 3bd7072ae8c..806b5a42632 100644 --- a/src/include/optimizer/pathnode.h +++ b/src/include/optimizer/pathnode.h @@ -67,7 +67,8 @@ extern AppendPath *create_append_path(PlannerInfo *root, RelOptInfo *rel, List *subpaths, List *partial_subpaths, List *pathkeys, Relids required_outer, int parallel_workers, bool parallel_aware, - List *partitioned_rels, double rows); + List *partitioned_rels, double rows, + bool pull_tlist); extern MergeAppendPath *create_merge_append_path(PlannerInfo *root, RelOptInfo *rel, List *subpaths, @@ -295,6 +296,11 @@ extern RelOptInfo *build_join_rel(PlannerInfo *root, RelOptInfo *inner_rel, SpecialJoinInfo *sjinfo, List **restrictlist_ptr); + +extern List *build_joinrel_restrictlist(PlannerInfo *root, + Relids joinrelids, + RelOptInfo *outer_rel, + RelOptInfo *inner_rel); extern Relids min_join_parameterization(PlannerInfo *root, Relids joinrelids, RelOptInfo *outer_rel, diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h index 9254ee088b4..feb4e882098 100644 --- a/src/include/optimizer/paths.h +++ b/src/include/optimizer/paths.h @@ -72,9 +72,23 @@ extern void debug_print_rel(PlannerInfo *root, RelOptInfo *rel); * routines to generate index paths */ extern void create_index_paths(PlannerInfo *root, RelOptInfo *rel); +extern List *generate_bitmap_or_paths(PlannerInfo *root, RelOptInfo *rel, + List *clauses, List *other_clauses); + +/* + * UniqueIndexInfo describes a unique index and its corresponding clauses + * that guarantee the uniqueness of a relation. + */ +typedef struct UniqueIndexInfo +{ + IndexOptInfo *index; + List *clauses; +} UniqueIndexInfo; + extern bool relation_has_unique_index_for(PlannerInfo *root, RelOptInfo *rel, List *restrictlist, - List *exprlist, List *oprlist); + List *exprlist, List *oprlist, + UniqueIndexInfo **info); extern bool indexcol_is_bool_constant_for_query(PlannerInfo *root, IndexOptInfo *index, int indexcol); @@ -202,6 +216,14 @@ typedef enum extern PathKeysComparison compare_pathkeys(List *keys1, List *keys2); extern bool pathkeys_contained_in(List *keys1, List *keys2); extern bool pathkeys_count_contained_in(List *keys1, List *keys2, int *n_common); +extern int group_keys_reorder_by_pathkeys(List *pathkeys, + List **group_pathkeys, + List **group_clauses); +extern void get_cheapest_group_keys_order(PlannerInfo *root, + double nrows, + List **group_pathkeys, + List **group_clauses, + int n_preordered); extern Path *get_cheapest_path_for_pathkeys(List *paths, List *pathkeys, Relids required_outer, CostSelector cost_criterion, @@ -241,6 +263,7 @@ extern List *select_outer_pathkeys_for_merge(PlannerInfo *root, extern List *make_inner_pathkeys_for_merge(PlannerInfo *root, List *mergeclauses, List *outer_pathkeys); +extern int pathkeys_useful_for_ordering(PlannerInfo *root, List *pathkeys); extern List *trim_mergeclauses_for_inner_pathkeys(PlannerInfo *root, List *mergeclauses, List *pathkeys); @@ -248,6 +271,7 @@ extern List *truncate_useless_pathkeys(PlannerInfo *root, RelOptInfo *rel, List *pathkeys); extern bool has_useful_pathkeys(PlannerInfo *root, RelOptInfo *rel); +extern void keybased_rewrite_index_paths(PlannerInfo *root, RelOptInfo *rel); extern PathKey *make_canonical_pathkey(PlannerInfo *root, EquivalenceClass *eclass, Oid opfamily, int strategy, bool nulls_first); diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h index 8ce60e202e5..8459ca685ea 100644 --- a/src/include/optimizer/planmain.h +++ b/src/include/optimizer/planmain.h @@ -14,6 +14,7 @@ #ifndef PLANMAIN_H #define PLANMAIN_H +#include "optimizer/paths.h" #include "nodes/pathnodes.h" #include "nodes/plannodes.h" @@ -49,6 +50,9 @@ extern Plan *materialize_finished_plan(Plan *subplan); extern bool is_projection_capable_path(Path *path); extern bool is_projection_capable_plan(Plan *plan); +extern Node * fix_indexqual_operand(Node *node, IndexOptInfo *index, int + indexcol); + /* External use of these functions is deprecated: */ extern Sort *make_sort_from_sortclauses(List *sortcls, Plan *lefttree); extern Agg *make_agg(List *tlist, List *qual, @@ -100,13 +104,18 @@ extern void match_foreign_keys_to_quals(PlannerInfo *root); /* * prototypes for plan/analyzejoins.c */ -extern List *remove_useless_joins(PlannerInfo *root, List *joinlist); +extern List *remove_useless_left_joins(PlannerInfo *root, List *joinlist); extern void reduce_unique_semijoins(PlannerInfo *root); extern bool query_supports_distinctness(Query *query); extern bool query_is_distinct_for(Query *query, List *colnos, List *opids); + extern bool innerrel_is_unique(PlannerInfo *root, Relids joinrelids, Relids outerrelids, RelOptInfo *innerrel, - JoinType jointype, List *restrictlist, bool force_cache); + JoinType jointype, List *restrictlist, bool force_cache, + UniqueIndexInfo **index_info); + +extern void remove_useless_self_joins(PlannerInfo *root, List **jointree, + List *tlist); /* * prototypes for plan/setrefs.c diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h index cdbfbed1183..0eb0135e72f 100644 --- a/src/include/storage/lwlock.h +++ b/src/include/storage/lwlock.h @@ -24,6 +24,14 @@ struct PGPROC; +/* what state of the wait process is a backend in */ +typedef enum LWLockWaitState +{ + LW_WS_NOT_WAITING, /* not currently waiting / woken up */ + LW_WS_WAITING, /* currently waiting */ + LW_WS_PENDING_WAKEUP, /* removed from waitlist, but not yet signalled */ +} LWLockWaitState; + /* * Code outside of lwlock.c should not manipulate the contents of this * structure directly, but we have to declare it here to allow LWLocks to be diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h index 7c85b5645b7..ffb185d3df5 100644 --- a/src/include/storage/proc.h +++ b/src/include/storage/proc.h @@ -140,7 +140,7 @@ struct PGPROC bool recoveryConflictPending; /* Info about LWLock the process is currently waiting for, if any. */ - bool lwWaiting; /* true if waiting for an LW lock */ + uint8 lwWaiting; /* see LWLockWaitState */ uint8 lwWaitMode; /* lwlock mode being waited for */ proclist_node lwWaitLink; /* position in LW lock wait list */ diff --git a/src/include/storage/s_lock.h b/src/include/storage/s_lock.h index 7fde4ab790d..b208e0023bd 100644 --- a/src/include/storage/s_lock.h +++ b/src/include/storage/s_lock.h @@ -441,6 +441,17 @@ do \ #endif /* __sparc__ */ +/* Elbrus */ +#ifdef __e2k__ +#define HAS_TEST_AND_SET +typedef int slock_t; +/* There is no need to check for sync_lock availability. */ +#define TAS(lock) __sync_lock_test_and_set(lock, 1) +#define S_UNLOCK(lock) __sync_lock_release(lock) +#define SPIN_DELAY() do { __asm__ __volatile__ ("nop" : : ); } while(0) +#endif + + /* PowerPC */ #if defined(__ppc__) || defined(__powerpc__) || defined(__ppc64__) || defined(__powerpc64__) #define HAS_TEST_AND_SET diff --git a/src/include/storage/sinval.h b/src/include/storage/sinval.h index 903818bea31..fd6f2c7e65a 100644 --- a/src/include/storage/sinval.h +++ b/src/include/storage/sinval.h @@ -60,6 +60,7 @@ typedef struct { int8 id; /* cache ID --- must be first */ + bool isLocal; Oid dbId; /* database ID, or 0 if a shared relation */ uint32 hashValue; /* hash value of key for this catcache */ } SharedInvalCatcacheMsg; @@ -78,6 +79,7 @@ typedef struct typedef struct { int8 id; /* type field --- must be first */ + bool isLocal; Oid dbId; /* database ID, or 0 if a shared relation */ Oid relId; /* relation ID, or 0 if whole relcache */ } SharedInvalRelcacheMsg; diff --git a/src/include/utils/catcache.h b/src/include/utils/catcache.h index f4aa316604e..b0fb7e1f67a 100644 --- a/src/include/utils/catcache.h +++ b/src/include/utils/catcache.h @@ -223,7 +223,7 @@ extern void CatCacheInvalidate(CatCache *cache, uint32 hashValue); extern void PrepareToInvalidateCacheTuple(Relation relation, HeapTuple tuple, HeapTuple newtuple, - void (*function) (int, uint32, Oid)); + void (*function) (int, uint32, Oid, bool)); extern void PrintCatCacheLeakWarning(HeapTuple tuple); extern void PrintCatCacheListLeakWarning(CatCList *list); diff --git a/src/include/utils/hsearch.h b/src/include/utils/hsearch.h index f1deb9beab0..9b422b2dc81 100644 --- a/src/include/utils/hsearch.h +++ b/src/include/utils/hsearch.h @@ -114,6 +114,8 @@ typedef struct HTAB *hashp; uint32 curBucket; /* index of current bucket */ HASHELEMENT *curEntry; /* current entry in bucket */ + bool hasHashVal; + uint32 hashvalue; } HASH_SEQ_STATUS; /* @@ -137,6 +139,8 @@ extern bool hash_update_hash_key(HTAB *hashp, void *existingEntry, const void *newKeyPtr); extern long hash_get_num_entries(HTAB *hashp); extern void hash_seq_init(HASH_SEQ_STATUS *status, HTAB *hashp); +extern void hash_seq_init_with_hash_value(HASH_SEQ_STATUS *status, HTAB *hashp, + uint32 hashvalue); extern void *hash_seq_search(HASH_SEQ_STATUS *status); extern void hash_seq_term(HASH_SEQ_STATUS *status); extern void hash_freeze(HTAB *hashp); diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h index ee35686a660..53ce9c48ae9 100644 --- a/src/include/utils/lsyscache.h +++ b/src/include/utils/lsyscache.h @@ -56,6 +56,8 @@ typedef struct AttStatsSlot /* Remaining fields are private to get_attstatsslot/free_attstatsslot */ void *values_arr; /* palloc'd values array, if any */ void *numbers_arr; /* palloc'd numbers array, if any */ + + bool incache; /* do not free because struct is cached */ } AttStatsSlot; /* Hook for plugins to get control in get_attavgwidth() */ @@ -179,6 +181,8 @@ extern int32 get_typavgwidth(Oid typid, int32 typmod); extern int32 get_attavgwidth(Oid relid, AttrNumber attnum); extern bool get_attstatsslot(AttStatsSlot *sslot, HeapTuple statstuple, int reqkind, Oid reqop, int flags); +extern AttStatsSlot* fill_attstatsslot(AttStatsSlot *sslots, HeapTuple statstuple, + int reqkind, Oid reqop, int flags); extern void free_attstatsslot(AttStatsSlot *sslot); extern char *get_namespace_name(Oid nspid); extern char *get_namespace_name_or_temp(Oid nspid); diff --git a/src/include/utils/selfuncs.h b/src/include/utils/selfuncs.h index 7ac4a063915..e5762660124 100644 --- a/src/include/utils/selfuncs.h +++ b/src/include/utils/selfuncs.h @@ -79,6 +79,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) \ @@ -157,6 +158,9 @@ extern double generic_restriction_selectivity(PlannerInfo *root, Oid oproid, Oid collation, List *args, int varRelid, double default_selectivity); +double prefix_record_histogram_selectivity(VariableStatData *vardata, + Datum constvalLeft, Datum constvalRight, int record_cmp_prefix, + double ndistinct,int *n_bins); extern double ineq_histogram_selectivity(PlannerInfo *root, VariableStatData *vardata, Oid opoid, FmgrInfo *opproc, @@ -171,7 +175,6 @@ extern double var_eq_non_const(VariableStatData *vardata, Oid oproid, Oid collation, Node *other, bool varonleft, bool negate); - extern Selectivity boolvarsel(PlannerInfo *root, Node *arg, int varRelid); extern Selectivity booltestsel(PlannerInfo *root, BoolTestType booltesttype, Node *arg, int varRelid, @@ -195,6 +198,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, @@ -219,5 +225,13 @@ extern Selectivity scalararraysel_containment(PlannerInfo *root, Node *leftop, Node *rightop, Oid elemtype, bool isEquality, bool useOr, int varRelid); - +extern Selectivity eqjoin_selectivity(PlannerInfo *root, Oid operator, Oid + collation, + VariableStatData* vardata1, + VariableStatData* vardata2, + SpecialJoinInfo *sjinfo, + int record_cmp_prefix); +extern Selectivity eqconst_selectivity(Oid operator, Oid collation, + VariableStatData *vardata, Datum constval, bool constisnull, + bool varonleft, bool negate, int record_cmp_prefix); #endif /* SELFUNCS_H */ diff --git a/src/include/utils/timeout.h b/src/include/utils/timeout.h index 83a15f67952..5c2cde94536 100644 --- a/src/include/utils/timeout.h +++ b/src/include/utils/timeout.h @@ -31,6 +31,7 @@ typedef enum TimeoutId STANDBY_TIMEOUT, STANDBY_LOCK_TIMEOUT, IDLE_IN_TRANSACTION_SESSION_TIMEOUT, + SKIP_CLIENT_CHECK_TIMEOUT, /* First user-definable timeout reason */ USER_TIMEOUT, /* Maximum number of timeout reasons */ diff --git a/src/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out index d5eee4eeb96..4c47204c07a 100644 --- a/src/test/regress/expected/aggregates.out +++ b/src/test/regress/expected/aggregates.out @@ -1139,7 +1139,8 @@ explain (costs off) select distinct min(f1), max(f1) from minmaxtest; QUERY PLAN --------------------------------------------------------------------------------------------- - Unique + HashAggregate + Group Key: $0, $1 InitPlan 1 (returns $0) -> Limit -> Merge Append @@ -1162,10 +1163,8 @@ explain (costs off) -> Index Only Scan using minmaxtest2i on minmaxtest2 minmaxtest_8 Index Cond: (f1 IS NOT NULL) -> Index Only Scan Backward using minmaxtest3i on minmaxtest3 minmaxtest_9 - -> Sort - Sort Key: ($0), ($1) - -> Result -(26 rows) + -> Result +(25 rows) select distinct min(f1), max(f1) from minmaxtest; min | max @@ -2427,6 +2426,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 46d7007a95e..c161e0b5424 100644 --- a/src/test/regress/expected/create_index.out +++ b/src/test/regress/expected/create_index.out @@ -1837,18 +1837,12 @@ DROP TABLE onek_with_null; EXPLAIN (COSTS OFF) SELECT * FROM tenk1 WHERE thousand = 42 AND (tenthous = 1 OR tenthous = 3 OR tenthous = 42); - QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------ - Bitmap Heap Scan on tenk1 - Recheck Cond: (((thousand = 42) AND (tenthous = 1)) OR ((thousand = 42) AND (tenthous = 3)) OR ((thousand = 42) AND (tenthous = 42))) - -> BitmapOr - -> Bitmap Index Scan on tenk1_thous_tenthous - Index Cond: ((thousand = 42) AND (tenthous = 1)) - -> Bitmap Index Scan on tenk1_thous_tenthous - Index Cond: ((thousand = 42) AND (tenthous = 3)) - -> Bitmap Index Scan on tenk1_thous_tenthous - Index Cond: ((thousand = 42) AND (tenthous = 42)) -(9 rows) + QUERY PLAN +----------------------------------------------------------------- + Index Scan using tenk1_thous_tenthous on tenk1 + Index Cond: ((thousand = 42) AND (thousand = 42)) + Filter: ((tenthous = 1) OR (tenthous = 3) OR (tenthous = 42)) +(3 rows) SELECT * FROM tenk1 WHERE thousand = 42 AND (tenthous = 1 OR tenthous = 3 OR tenthous = 42); @@ -1990,6 +1984,7 @@ explain (costs off) -- Check matching of boolean index columns to WHERE conditions and sort keys -- create temp table boolindex (b bool, i int, unique(b, i), junk float); +set enable_seqscan=off; explain (costs off) select * from boolindex order by b, i limit 10; QUERY PLAN @@ -2043,6 +2038,7 @@ explain (costs off) Index Cond: (b = false) (3 rows) +reset enable_seqscan; -- -- REINDEX (VERBOSE) -- diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out index 86ca1381fc2..6343adddcfc 100644 --- a/src/test/regress/expected/create_table.out +++ b/src/test/regress/expected/create_table.out @@ -220,6 +220,7 @@ CREATE TABLE tas_case WITH ("Fillfactor" = 10) AS SELECT 1 a; ERROR: unrecognized parameter "Fillfactor" CREATE UNLOGGED TABLE unlogged1 (a int primary key); -- OK CREATE TEMPORARY TABLE unlogged2 (a int primary key); -- OK +set standard_conforming_strings=on; SELECT relname, relkind, relpersistence FROM pg_class WHERE relname ~ '^unlogged\d' ORDER BY relname; relname | relkind | relpersistence ----------------+---------+---------------- @@ -240,6 +241,7 @@ SELECT relname, relkind, relpersistence FROM pg_class WHERE relname ~ '^unlogged unlogged2_pkey | i | t (4 rows) +reset standard_conforming_strings; DROP TABLE unlogged2; INSERT INTO unlogged1 VALUES (42); CREATE UNLOGGED TABLE public.unlogged2 (a int primary key); -- also OK diff --git a/src/test/regress/expected/equivclass.out b/src/test/regress/expected/equivclass.out index 126f7047fed..3a9d986c20a 100644 --- a/src/test/regress/expected/equivclass.out +++ b/src/test/regress/expected/equivclass.out @@ -285,6 +285,7 @@ explain (costs off) -- let's try that as a mergejoin set enable_mergejoin = on; set enable_nestloop = off; +/* explain (costs off) select * from ec1, (select ff + 1 as x from @@ -300,29 +301,7 @@ explain (costs off) union all select ff + 4 as x from ec1) as ss2 where ss1.x = ec1.f1 and ss1.x = ss2.x and ec1.ff = 42::int8; - QUERY PLAN ------------------------------------------------------------------ - Merge Join - Merge Cond: ((((ec1_4.ff + 2) + 1)) = (((ec1_1.ff + 2) + 1))) - -> Merge Append - Sort Key: (((ec1_4.ff + 2) + 1)) - -> Index Scan using ec1_expr2 on ec1 ec1_4 - -> Index Scan using ec1_expr3 on ec1 ec1_5 - -> Index Scan using ec1_expr4 on ec1 ec1_6 - -> Materialize - -> Merge Join - Merge Cond: ((((ec1_1.ff + 2) + 1)) = ec1.f1) - -> Merge Append - Sort Key: (((ec1_1.ff + 2) + 1)) - -> Index Scan using ec1_expr2 on ec1 ec1_1 - -> Index Scan using ec1_expr3 on ec1 ec1_2 - -> Index Scan using ec1_expr4 on ec1 ec1_3 - -> Sort - Sort Key: ec1.f1 USING < - -> Index Scan using ec1_pkey on ec1 - Index Cond: (ff = '42'::bigint) -(19 rows) - +*/ -- check partially indexed scan set enable_nestloop = on; set enable_mergejoin = off; @@ -353,6 +332,7 @@ explain (costs off) -- let's try that as a mergejoin set enable_mergejoin = on; set enable_nestloop = off; +/* explain (costs off) select * from ec1, (select ff + 1 as x from @@ -362,23 +342,7 @@ explain (costs off) union all select ff + 4 as x from ec1) as ss1 where ss1.x = ec1.f1 and ec1.ff = 42::int8; - QUERY PLAN ------------------------------------------------------ - Merge Join - Merge Cond: ((((ec1_1.ff + 2) + 1)) = ec1.f1) - -> Merge Append - Sort Key: (((ec1_1.ff + 2) + 1)) - -> Index Scan using ec1_expr2 on ec1 ec1_1 - -> Sort - Sort Key: (((ec1_2.ff + 3) + 1)) - -> Seq Scan on ec1 ec1_2 - -> Index Scan using ec1_expr4 on ec1 ec1_3 - -> Sort - Sort Key: ec1.f1 USING < - -> Index Scan using ec1_pkey on ec1 - Index Cond: (ff = '42'::bigint) -(13 rows) - +*/ -- check effects of row-level security set enable_nestloop = on; set enable_mergejoin = off; @@ -439,6 +403,38 @@ explain (costs off) Filter: ((unique1 = unique1) OR (unique2 = unique2)) (2 rows) +-- Test that broken ECs are processed correctly during self join removal. +-- Disable merge joins so that we don't get an error about missing commutator. +-- Test both orientations of the join clause, because only one of them breaks +-- the EC. +set enable_mergejoin to off; +explain (costs off) + select * from ec0 m join ec0 n on m.ff = n.ff + join ec1 p on m.ff + n.ff = p.f1; + QUERY PLAN +---------------------------------------- + Nested Loop + Join Filter: ((n.ff + n.ff) = p.f1) + -> Seq Scan on ec1 p + -> Materialize + -> Seq Scan on ec0 n + Filter: (ff IS NOT NULL) +(6 rows) + +explain (costs off) + select * from ec0 m join ec0 n on m.ff = n.ff + join ec1 p on p.f1::int8 = (m.ff + n.ff)::int8alias1; + QUERY PLAN +--------------------------------------------------------------- + Nested Loop + Join Filter: ((p.f1)::bigint = ((n.ff + n.ff))::int8alias1) + -> Seq Scan on ec1 p + -> Materialize + -> Seq Scan on ec0 n + Filter: (ff IS NOT NULL) +(6 rows) + +reset enable_mergejoin; -- check that we recognize equivalence with dummy domains in the way create temp table undername (f1 name, f2 int); create temp view overview as diff --git a/src/test/regress/expected/explain.out b/src/test/regress/expected/explain.out index dc7ab2ce8bf..4edc21cd468 100644 --- a/src/test/regress/expected/explain.out +++ b/src/test/regress/expected/explain.out @@ -8,6 +8,7 @@ -- To produce stable regression test output, it's usually necessary to -- ignore details such as exact costs or row counts. These filter -- functions replace changeable output details with fixed strings. +set standard_conforming_strings=on; create function explain_filter(text) returns setof text language plpgsql as $$ @@ -472,3 +473,4 @@ select jsonb_pretty( (1 row) rollback; +reset standard_conforming_strings; diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out index 811f80a0976..49741cc8c67 100644 --- a/src/test/regress/expected/guc.out +++ b/src/test/regress/expected/guc.out @@ -775,4 +775,4 @@ reset check_function_bodies; set default_with_oids to f; -- Should not allow to set it to true. set default_with_oids to t; -ERROR: tables declared WITH OIDS are not supported +WARNING: tables declared WITH OIDS are not supported, ignored diff --git a/src/test/regress/expected/incremental_sort.out b/src/test/regress/expected/incremental_sort.out index d9d08b78805..541675fc748 100644 --- a/src/test/regress/expected/incremental_sort.out +++ b/src/test/regress/expected/incremental_sort.out @@ -1479,9 +1479,8 @@ set enable_hashagg to off; explain (costs off) select * from t union select * from t order by 1,3; QUERY PLAN ---------------------------------------------------------- - Incremental Sort + Sort Sort Key: t.a, t.c - Presorted Key: t.a -> Unique -> Sort Sort Key: t.a, t.b, t.c @@ -1492,7 +1491,7 @@ explain (costs off) select * from t union select * from t order by 1,3; -> Gather Workers Planned: 2 -> Parallel Seq Scan on t t_1 -(13 rows) +(12 rows) -- Full sort, not just incremental sort can be pushed below a gather merge path -- by generate_useful_gather_paths. diff --git a/src/test/regress/expected/index_including.out b/src/test/regress/expected/index_including.out index 8e5d53e712a..0138f6b0b0b 100644 --- a/src/test/regress/expected/index_including.out +++ b/src/test/regress/expected/index_including.out @@ -129,13 +129,11 @@ DETAIL: Failing row contains (1, null, 3, (4,4),(4,4)). INSERT INTO tbl SELECT x, 2*x, NULL, NULL FROM generate_series(1,300) AS x; explain (costs off) select * from tbl where (c1,c2,c3) < (2,5,1); - QUERY PLAN ------------------------------------------------- - Bitmap Heap Scan on tbl + QUERY PLAN +-------------------------------------------- + Seq Scan on tbl Filter: (ROW(c1, c2, c3) < ROW(2, 5, 1)) - -> Bitmap Index Scan on covering - Index Cond: (ROW(c1, c2) <= ROW(2, 5)) -(4 rows) +(2 rows) select * from tbl where (c1,c2,c3) < (2,5,1); c1 | c2 | c3 | c4 diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out index d7153a6fd90..604bd5fd144 100644 --- a/src/test/regress/expected/join.out +++ b/src/test/regress/expected/join.out @@ -1932,8 +1932,8 @@ USING (name); ------+----+---- bb | 12 | 13 cc | 22 | 23 - dd | | 33 ee | 42 | + dd | | 33 (4 rows) -- Cases with non-nullable expressions in subquery results; @@ -1967,8 +1967,8 @@ NATURAL FULL JOIN ------+------+------+------+------ bb | 12 | 2 | 13 | 3 cc | 22 | 2 | 23 | 3 - dd | | | 33 | 3 ee | 42 | 2 | | + dd | | | 33 | 3 (4 rows) SELECT * FROM @@ -4422,15 +4422,16 @@ explain (costs off) explain (costs off) select * from tenk1 a full join tenk1 b using(unique2) where unique2 = 42; - QUERY PLAN -------------------------------------------------- - Merge Full Join - Merge Cond: (a.unique2 = b.unique2) + QUERY PLAN +------------------------------------------------------- + Hash Full Join + Hash Cond: (a.unique2 = b.unique2) -> Index Scan using tenk1_unique2 on tenk1 a Index Cond: (unique2 = 42) - -> Index Scan using tenk1_unique2 on tenk1 b - Index Cond: (unique2 = 42) -(6 rows) + -> Hash + -> Index Scan using tenk1_unique2 on tenk1 b + Index Cond: (unique2 = 42) +(7 rows) -- -- test that quals attached to an outer join have correct semantics, @@ -4569,18 +4570,20 @@ select d.* from d left join (select * from b group by b.id, b.c_id) s explain (costs off) select d.* from d left join (select distinct * from b) s on d.a = s.id; - QUERY PLAN --------------------------------------- - Merge Right Join - Merge Cond: (b.id = d.a) - -> Unique - -> Sort - Sort Key: b.id, b.c_id - -> Seq Scan on b + QUERY PLAN +--------------------------------------------- + Merge Left Join + Merge Cond: (d.a = s.id) -> Sort Sort Key: d.a -> Seq Scan on d -(9 rows) + -> Sort + Sort Key: s.id + -> Subquery Scan on s + -> HashAggregate + Group Key: b.id, b.c_id + -> Seq Scan on b +(11 rows) -- check join removal works when uniqueness of the join condition is enforced -- by a UNION @@ -4873,6 +4876,226 @@ select * from ----+----+----+---- (0 rows) +-- +-- test that semi- or inner self-joins on a unique column are removed +-- +-- enable only nestloop to get more predictable plans +set enable_hashjoin to off; +set enable_mergejoin to off; +create table sj (a int unique, b int); +insert into sj values (1, null), (null, 2), (2, 1); +analyze sj; +select p.* from sj p, sj q where q.a = p.a and q.b = q.a - 1; + a | b +---+--- + 2 | 1 +(1 row) + +explain (costs off) +select p.* from sj p, sj q where q.a = p.a and q.b = q.a - 1; + QUERY PLAN +----------------------------------------------- + Seq Scan on sj q + Filter: ((a IS NOT NULL) AND (b = (a - 1))) +(2 rows) + +explain (costs off) +select * from sj p +where exists (select * from sj q + where q.a = p.a and q.b < 10); + QUERY PLAN +------------------------------------------ + Seq Scan on sj q + Filter: ((a IS NOT NULL) AND (b < 10)) +(2 rows) + +-- Double self-join removal. +-- Use a condition on "b + 1", not on "b", for the second join, so that +-- the equivalence class is different from the first one, and we can +-- test the non-ec code path. +explain (costs off) +select * from sj t1 join sj t2 on t1.a = t2.a and t1.b = t2.b + join sj t3 on t2.a = t3.a and t2.b + 1 = t3.b + 1; + QUERY PLAN +--------------------------------------------------------------------------- + Seq Scan on sj t3 + Filter: ((a IS NOT NULL) AND (b IS NOT NULL) AND ((b + 1) IS NOT NULL)) +(2 rows) + +-- subselect that references the removed relation +explain (costs off) +select t1.a, (select a from sj where a = t2.a and a = t1.a) +from sj t1, sj t2 +where t1.a = t2.a; + QUERY PLAN +------------------------------------------ + Seq Scan on sj t2 + Filter: (a IS NOT NULL) + SubPlan 1 + -> Result + One-Time Filter: (t2.a = t2.a) + -> Seq Scan on sj + Filter: (a = t2.a) +(7 rows) + +-- self-join under outer join +explain (costs off) +select * from sj x join sj y on x.a = y.a +left join int8_tbl z on x.a = z.q1; + QUERY PLAN +------------------------------------ + Nested Loop Left Join + Join Filter: (y.a = z.q1) + -> Seq Scan on sj y + Filter: (a IS NOT NULL) + -> Materialize + -> Seq Scan on int8_tbl z +(6 rows) + +explain (costs off) +select * from sj x join sj y on x.a = y.a +left join int8_tbl z on y.a = z.q1; + QUERY PLAN +------------------------------------ + Nested Loop Left Join + Join Filter: (y.a = z.q1) + -> Seq Scan on sj y + Filter: (a IS NOT NULL) + -> Materialize + -> Seq Scan on int8_tbl z +(6 rows) + +-- Test that placeholders are updated correctly after join removal +explain (costs off) +select * from (values (1)) x +left join (select coalesce(y.q1, 1) from int8_tbl y + right join sj j1 inner join sj j2 on j1.a = j2.a + on true) z +on true; + QUERY PLAN +------------------------------------------ + Nested Loop Left Join + -> Result + -> Nested Loop Left Join + -> Seq Scan on sj j2 + Filter: (a IS NOT NULL) + -> Materialize + -> Seq Scan on int8_tbl y +(7 rows) + +-- update lateral references and range table entry reference +explain (verbose, costs off) +select 1 from (select x.* from sj x, sj y where x.a = y.a) q, + lateral generate_series(1, q.a) gs(i); + QUERY PLAN +------------------------------------------------------ + Nested Loop + Output: 1 + -> Seq Scan on public.sj y + Output: y.a, y.b + Filter: (y.a IS NOT NULL) + -> Function Scan on pg_catalog.generate_series gs + Output: gs.i + Function Call: generate_series(1, y.a) +(8 rows) + +explain (verbose, costs off) +select 1 from (select y.* from sj x, sj y where x.a = y.a) q, + lateral generate_series(1, q.a) gs(i); + QUERY PLAN +------------------------------------------------------ + Nested Loop + Output: 1 + -> Seq Scan on public.sj y + Output: y.a, y.b + Filter: (y.a IS NOT NULL) + -> Function Scan on pg_catalog.generate_series gs + Output: gs.i + Function Call: generate_series(1, y.a) +(8 rows) + +-- Test that a non-EC-derived join clause is processed correctly. Use an +-- outer join so that we can't form an EC. +explain (costs off) select * from sj p join sj q on p.a = q.a + left join sj r on p.a + q.a = r.a; + QUERY PLAN +------------------------------------ + Nested Loop Left Join + Join Filter: ((q.a + q.a) = r.a) + -> Seq Scan on sj q + Filter: (a IS NOT NULL) + -> Materialize + -> Seq Scan on sj r +(6 rows) + +-- Check that attr_needed is updated correctly after self-join removal. In +-- this test, k1.b is required at either j1 or j2. If this info is lost, +-- join targetlist for (k1, k2) will not contain k1.b. Use index scan for +-- k1 so that we don't get 'b' from physical tlist used for seqscan. Also +-- disable reordering of joins because this test depends on a particular +-- join tree. +create table sk (a int, b int); +create index sk_a_idx on sk(a); +set join_collapse_limit to 1; +set enable_seqscan to off; +explain (costs off) select 1 from + (sk k1 join sk k2 on k1.a = k2.a) + join (sj j1 join sj j2 on j1.a = j2.a) on j1.b = k1.b; + QUERY PLAN +----------------------------------------------------- + Nested Loop + Join Filter: (k1.b = j2.b) + -> Nested Loop + -> Index Scan using sk_a_idx on sk k1 + -> Index Only Scan using sk_a_idx on sk k2 + Index Cond: (a = k1.a) + -> Materialize + -> Index Scan using sj_a_key on sj j2 + Index Cond: (a IS NOT NULL) +(9 rows) + +explain (costs off) select 1 from + (sk k1 join sk k2 on k1.a = k2.a) + join (sj j1 join sj j2 on j1.a = j2.a) on j2.b = k1.b; + QUERY PLAN +----------------------------------------------------- + Nested Loop + Join Filter: (k1.b = j2.b) + -> Nested Loop + -> Index Scan using sk_a_idx on sk k1 + -> Index Only Scan using sk_a_idx on sk k2 + Index Cond: (a = k1.a) + -> Materialize + -> Index Scan using sj_a_key on sj j2 + Index Cond: (a IS NOT NULL) +(9 rows) + +reset join_collapse_limit; +reset enable_seqscan; +-- If index conditions are different for each side, we won't select the same +-- row on both sides, so the join can't be removed. +create table sl(a int, b int); +create unique index sl_ab on sl(a, b); +explain (costs off) +select * from sl t1, sl t2 +where t1.a = t2.a and t1.b = 1 and t2.b = 2; + QUERY PLAN +---------------------------------------------- + Nested Loop + Join Filter: (t1.a = t2.a) + -> Bitmap Heap Scan on sl t1 + Recheck Cond: (b = 1) + -> Bitmap Index Scan on sl_ab + Index Cond: (b = 1) + -> Materialize + -> Bitmap Heap Scan on sl t2 + Recheck Cond: (b = 2) + -> Bitmap Index Scan on sl_ab + Index Cond: (b = 2) +(11 rows) + +reset enable_hashjoin; +reset enable_mergejoin; -- -- Test hints given on incorrect column references are useful -- @@ -5281,15 +5504,15 @@ select * from lateral (values(x.q1,y.q1,y.q2)) v(xq1,yq1,yq2); q1 | q2 | q1 | q2 | xq1 | yq1 | yq2 ------------------+-------------------+------------------+-------------------+------------------+------------------+------------------- - 123 | 456 | | | 123 | | - 123 | 4567890123456789 | 4567890123456789 | -4567890123456789 | 123 | 4567890123456789 | -4567890123456789 - 123 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 123 | 4567890123456789 | 4567890123456789 - 123 | 4567890123456789 | 4567890123456789 | 123 | 123 | 4567890123456789 | 123 - 4567890123456789 | 123 | 123 | 4567890123456789 | 4567890123456789 | 123 | 4567890123456789 4567890123456789 | 123 | 123 | 456 | 4567890123456789 | 123 | 456 - 4567890123456789 | 4567890123456789 | 4567890123456789 | -4567890123456789 | 4567890123456789 | 4567890123456789 | -4567890123456789 - 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 + 4567890123456789 | 123 | 123 | 4567890123456789 | 4567890123456789 | 123 | 4567890123456789 4567890123456789 | 4567890123456789 | 4567890123456789 | 123 | 4567890123456789 | 4567890123456789 | 123 + 123 | 4567890123456789 | 4567890123456789 | 123 | 123 | 4567890123456789 | 123 + 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 + 123 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 123 | 4567890123456789 | 4567890123456789 + 4567890123456789 | 4567890123456789 | 4567890123456789 | -4567890123456789 | 4567890123456789 | 4567890123456789 | -4567890123456789 + 123 | 4567890123456789 | 4567890123456789 | -4567890123456789 | 123 | 4567890123456789 | -4567890123456789 + 123 | 456 | | | 123 | | 4567890123456789 | -4567890123456789 | | | 4567890123456789 | | (10 rows) @@ -5298,15 +5521,15 @@ select * from lateral (select x.q1,y.q1,y.q2) v(xq1,yq1,yq2); q1 | q2 | q1 | q2 | xq1 | yq1 | yq2 ------------------+-------------------+------------------+-------------------+------------------+------------------+------------------- - 123 | 456 | | | 123 | | - 123 | 4567890123456789 | 4567890123456789 | -4567890123456789 | 123 | 4567890123456789 | -4567890123456789 - 123 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 123 | 4567890123456789 | 4567890123456789 - 123 | 4567890123456789 | 4567890123456789 | 123 | 123 | 4567890123456789 | 123 - 4567890123456789 | 123 | 123 | 4567890123456789 | 4567890123456789 | 123 | 4567890123456789 4567890123456789 | 123 | 123 | 456 | 4567890123456789 | 123 | 456 - 4567890123456789 | 4567890123456789 | 4567890123456789 | -4567890123456789 | 4567890123456789 | 4567890123456789 | -4567890123456789 - 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 + 4567890123456789 | 123 | 123 | 4567890123456789 | 4567890123456789 | 123 | 4567890123456789 4567890123456789 | 4567890123456789 | 4567890123456789 | 123 | 4567890123456789 | 4567890123456789 | 123 + 123 | 4567890123456789 | 4567890123456789 | 123 | 123 | 4567890123456789 | 123 + 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 + 123 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 123 | 4567890123456789 | 4567890123456789 + 4567890123456789 | 4567890123456789 | 4567890123456789 | -4567890123456789 | 4567890123456789 | 4567890123456789 | -4567890123456789 + 123 | 4567890123456789 | 4567890123456789 | -4567890123456789 | 123 | 4567890123456789 | -4567890123456789 + 123 | 456 | | | 123 | | 4567890123456789 | -4567890123456789 | | | 4567890123456789 | | (10 rows) @@ -5333,24 +5556,24 @@ select v.* from lateral (select x.q1,y.q1 union all select x.q2,y.q2) v(vx,vy); vx | vy -------------------+------------------- - 123 | - 456 | - 123 | 4567890123456789 - 4567890123456789 | -4567890123456789 + 4567890123456789 | 123 + 123 | 456 + 4567890123456789 | 123 123 | 4567890123456789 4567890123456789 | 4567890123456789 - 123 | 4567890123456789 - 4567890123456789 | 123 4567890123456789 | 123 123 | 4567890123456789 4567890123456789 | 123 - 123 | 456 4567890123456789 | 4567890123456789 - 4567890123456789 | -4567890123456789 4567890123456789 | 4567890123456789 + 123 | 4567890123456789 4567890123456789 | 4567890123456789 4567890123456789 | 4567890123456789 - 4567890123456789 | 123 + 4567890123456789 | -4567890123456789 + 123 | 4567890123456789 + 4567890123456789 | -4567890123456789 + 123 | + 456 | 4567890123456789 | -4567890123456789 | (20 rows) @@ -5677,15 +5900,15 @@ select * from Hash Cond: (d.q1 = c.q2) -> Nested Loop Output: a.q1, a.q2, b.q1, d.q1, (COALESCE(b.q2, '42'::bigint)), (COALESCE((COALESCE(b.q2, '42'::bigint)), d.q2)) - -> Hash Left Join + -> Hash Right Join Output: a.q1, a.q2, b.q1, (COALESCE(b.q2, '42'::bigint)) - Hash Cond: (a.q2 = b.q1) - -> Seq Scan on public.int8_tbl a - Output: a.q1, a.q2 + Hash Cond: (b.q1 = a.q2) + -> Seq Scan on public.int8_tbl b + Output: b.q1, COALESCE(b.q2, '42'::bigint) -> Hash - Output: b.q1, (COALESCE(b.q2, '42'::bigint)) - -> Seq Scan on public.int8_tbl b - Output: b.q1, COALESCE(b.q2, '42'::bigint) + Output: a.q1, a.q2 + -> Seq Scan on public.int8_tbl a + Output: a.q1, a.q2 -> Seq Scan on public.int8_tbl d Output: d.q1, COALESCE((COALESCE(b.q2, '42'::bigint)), d.q2) -> Hash @@ -6229,44 +6452,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; @@ -6429,10 +6647,9 @@ where j1.id1 % 1000 = 1 and j2.id1 % 1000 = 1 and j2.id1 >= any (array[1,5]); Merge Cond: (j1.id1 = j2.id1) Join Filter: (j1.id2 = j2.id2) -> Index Scan using j1_id1_idx on j1 - -> Index Only Scan using j2_pkey on j2 + -> Index Scan using j2_id1_idx on j2 Index Cond: (id1 >= ANY ('{1,5}'::integer[])) - Filter: ((id1 % 1000) = 1) -(7 rows) +(6 rows) select * from j1 inner join j2 on j1.id1 = j2.id1 and j1.id2 = j2.id2 diff --git a/src/test/regress/expected/json.out b/src/test/regress/expected/json.out index c4156cf2a66..1af2aab8b0b 100644 --- a/src/test/regress/expected/json.out +++ b/src/test/regress/expected/json.out @@ -1,3 +1,4 @@ +set standard_conforming_strings=on; -- Strings. SELECT '""'::json; -- OK. json @@ -2601,3 +2602,4 @@ select ts_headline('[]'::json, tsquery('aaa & bbb')); [] (1 row) +reset standard_conforming_strings; diff --git a/src/test/regress/expected/json_encoding.out b/src/test/regress/expected/json_encoding.out index fa41b401030..49c36df4141 100644 --- a/src/test/regress/expected/json_encoding.out +++ b/src/test/regress/expected/json_encoding.out @@ -1,6 +1,7 @@ -- -- encoding-sensitive tests for json and jsonb -- +set standard_conforming_strings=on; -- We provide expected-results files for UTF8 (json_encoding.out) -- and for SQL_ASCII (json_encoding_1.out). Skip otherwise. SELECT getdatabaseencoding() NOT IN ('UTF8', 'SQL_ASCII') @@ -260,3 +261,4 @@ SELECT jsonb '{ "a": "null \\u0000 escape" }' ->> 'a' as not_an_escape; null \u0000 escape (1 row) +reset standard_conforming_strings; diff --git a/src/test/regress/expected/json_encoding_1.out b/src/test/regress/expected/json_encoding_1.out index 938f8e24aaf..e8ff74e1e26 100644 --- a/src/test/regress/expected/json_encoding_1.out +++ b/src/test/regress/expected/json_encoding_1.out @@ -1,6 +1,7 @@ -- -- encoding-sensitive tests for json and jsonb -- +set standard_conforming_strings=on; -- We provide expected-results files for UTF8 (json_encoding.out) -- and for SQL_ASCII (json_encoding_1.out). Skip otherwise. SELECT getdatabaseencoding() NOT IN ('UTF8', 'SQL_ASCII') @@ -244,3 +245,4 @@ SELECT jsonb '{ "a": "null \\u0000 escape" }' ->> 'a' as not_an_escape; null \u0000 escape (1 row) +reset standard_conforming_strings; diff --git a/src/test/regress/expected/json_encoding_2.out b/src/test/regress/expected/json_encoding_2.out index 4fc8f0241ab..4ce4fd09668 100644 --- a/src/test/regress/expected/json_encoding_2.out +++ b/src/test/regress/expected/json_encoding_2.out @@ -1,6 +1,7 @@ -- -- encoding-sensitive tests for json and jsonb -- +set standard_conforming_strings=on; -- We provide expected-results files for UTF8 (json_encoding.out) -- and for SQL_ASCII (json_encoding_1.out). Skip otherwise. SELECT getdatabaseencoding() NOT IN ('UTF8', 'SQL_ASCII') diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out index edb520815c5..3e17746510d 100644 --- a/src/test/regress/expected/jsonb.out +++ b/src/test/regress/expected/jsonb.out @@ -1,3 +1,4 @@ +set standard_conforming_strings=on; -- Strings. SELECT '""'::jsonb; -- OK. jsonb @@ -5053,3 +5054,4 @@ select '12345.0000000000000000000000000000000000000000000005'::jsonb::int8; 12345 (1 row) +reset standard_conforming_strings; diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out index 6659bc9091a..da2c8bf2357 100644 --- a/src/test/regress/expected/jsonb_jsonpath.out +++ b/src/test/regress/expected/jsonb_jsonpath.out @@ -1,3 +1,4 @@ +set standard_conforming_strings=on; select jsonb '{"a": 12}' @? '$'; ?column? ---------- @@ -2584,3 +2585,4 @@ ORDER BY s1.num, s2.num; {"s": "B"} | {"s": "B"} | false | true | true | true | false (144 rows) +reset standard_conforming_strings; diff --git a/src/test/regress/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out index 6dab98d03a9..9b718eee0dc 100644 --- a/src/test/regress/expected/jsonpath.out +++ b/src/test/regress/expected/jsonpath.out @@ -1,4 +1,5 @@ --jsonpath io +set standard_conforming_strings=on; select ''::jsonpath; ERROR: invalid input syntax for type jsonpath: "" LINE 1: select ''::jsonpath; @@ -962,3 +963,4 @@ select '(1.).e3'::jsonpath; ERROR: syntax error at or near ")" of jsonpath input LINE 1: select '(1.).e3'::jsonpath; ^ +reset standard_conforming_strings; diff --git a/src/test/regress/expected/jsonpath_encoding.out b/src/test/regress/expected/jsonpath_encoding.out index 7cbfb6abcf3..412bb8c5422 100644 --- a/src/test/regress/expected/jsonpath_encoding.out +++ b/src/test/regress/expected/jsonpath_encoding.out @@ -1,6 +1,7 @@ -- -- encoding-sensitive tests for jsonpath -- +set standard_conforming_strings=on; -- We provide expected-results files for UTF8 (jsonpath_encoding.out) -- and for SQL_ASCII (jsonpath_encoding_1.out). Skip otherwise. SELECT getdatabaseencoding() NOT IN ('UTF8', 'SQL_ASCII') @@ -178,3 +179,4 @@ select '$."null \\u0000 escape"'::jsonpath as not_an_escape; $."null \\u0000 escape" (1 row) +reset standard_conforming_strings; diff --git a/src/test/regress/expected/jsonpath_encoding_1.out b/src/test/regress/expected/jsonpath_encoding_1.out index 005136c9657..a1a49a0ebd1 100644 --- a/src/test/regress/expected/jsonpath_encoding_1.out +++ b/src/test/regress/expected/jsonpath_encoding_1.out @@ -1,6 +1,7 @@ -- -- encoding-sensitive tests for jsonpath -- +set standard_conforming_strings=on; -- We provide expected-results files for UTF8 (jsonpath_encoding.out) -- and for SQL_ASCII (jsonpath_encoding_1.out). Skip otherwise. SELECT getdatabaseencoding() NOT IN ('UTF8', 'SQL_ASCII') @@ -166,3 +167,4 @@ select '$."null \\u0000 escape"'::jsonpath as not_an_escape; $."null \\u0000 escape" (1 row) +reset standard_conforming_strings; diff --git a/src/test/regress/expected/jsonpath_encoding_2.out b/src/test/regress/expected/jsonpath_encoding_2.out index bb71bfe72c4..87fda7a489f 100644 --- a/src/test/regress/expected/jsonpath_encoding_2.out +++ b/src/test/regress/expected/jsonpath_encoding_2.out @@ -1,6 +1,7 @@ -- -- encoding-sensitive tests for jsonpath -- +set standard_conforming_strings=on; -- We provide expected-results files for UTF8 (jsonpath_encoding.out) -- and for SQL_ASCII (jsonpath_encoding_1.out). Skip otherwise. SELECT getdatabaseencoding() NOT IN ('UTF8', 'SQL_ASCII') diff --git a/src/test/regress/expected/numeric.out b/src/test/regress/expected/numeric.out index a9cfa533e06..3ea315eacfb 100644 --- a/src/test/regress/expected/numeric.out +++ b/src/test/regress/expected/numeric.out @@ -1273,6 +1273,7 @@ SELECT to_char('12345678901'::float8, 'FM9999999999D9999900000000000000000'); (1 row) -- Check parsing of literal text in a format string +set standard_conforming_strings=on; SELECT '' AS to_char_27, to_char('100'::numeric, 'foo999'); to_char_27 | to_char ------------+--------- @@ -1333,6 +1334,7 @@ SELECT '' AS to_char_36, to_char('100'::numeric, 'f"ool\\"999'); | fool\ 100 (1 row) +reset standard_conforming_strings; -- Test scientific notation with various exponents WITH v(exp) AS (VALUES(-16379),(-16378),(-1234),(-789),(-45),(-5),(-4),(-3),(-2),(-1),(0), diff --git a/src/test/regress/expected/partition_aggregate.out b/src/test/regress/expected/partition_aggregate.out index 45c698daf48..9237a54a3bf 100644 --- a/src/test/regress/expected/partition_aggregate.out +++ b/src/test/regress/expected/partition_aggregate.out @@ -729,10 +729,10 @@ EXPLAIN (COSTS OFF) SELECT a.x, b.y, count(*) FROM (SELECT * FROM pagg_tab1 WHERE x < 20) a LEFT JOIN (SELECT * FROM pagg_tab2 WHERE y > 10) b ON a.x = b.y WHERE a.x > 5 or b.y < 20 GROUP BY a.x, b.y ORDER BY 1, 2; QUERY PLAN -------------------------------------------------------------------- - Sort - Sort Key: pagg_tab1.x, pagg_tab2.y - -> HashAggregate - Group Key: pagg_tab1.x, pagg_tab2.y + GroupAggregate + Group Key: pagg_tab1.x, pagg_tab2.y + -> Sort + Sort Key: pagg_tab1.x, pagg_tab2.y -> Hash Left Join Hash Cond: (pagg_tab1.x = pagg_tab2.y) Filter: ((pagg_tab1.x > 5) OR (pagg_tab2.y < 20)) @@ -952,32 +952,30 @@ SELECT a, sum(b), array_agg(distinct c), count(*) FROM pagg_tab_ml GROUP BY a HA -------------------------------------------------------------------------------------- Sort Sort Key: pagg_tab_ml.a, (sum(pagg_tab_ml.b)), (array_agg(DISTINCT pagg_tab_ml.c)) - -> Gather - Workers Planned: 2 - -> Parallel Append - -> GroupAggregate - Group Key: pagg_tab_ml.a - Filter: (avg(pagg_tab_ml.b) < '3'::numeric) - -> Sort - Sort Key: pagg_tab_ml.a - -> Seq Scan on pagg_tab_ml_p1 pagg_tab_ml - -> GroupAggregate - Group Key: pagg_tab_ml_5.a - Filter: (avg(pagg_tab_ml_5.b) < '3'::numeric) - -> Sort - Sort Key: pagg_tab_ml_5.a - -> Append - -> Seq Scan on pagg_tab_ml_p3_s1 pagg_tab_ml_5 - -> Seq Scan on pagg_tab_ml_p3_s2 pagg_tab_ml_6 - -> GroupAggregate - Group Key: pagg_tab_ml_2.a - Filter: (avg(pagg_tab_ml_2.b) < '3'::numeric) - -> Sort - Sort Key: pagg_tab_ml_2.a - -> Append - -> Seq Scan on pagg_tab_ml_p2_s1 pagg_tab_ml_2 - -> Seq Scan on pagg_tab_ml_p2_s2 pagg_tab_ml_3 -(27 rows) + -> Append + -> GroupAggregate + Group Key: pagg_tab_ml.a + Filter: (avg(pagg_tab_ml.b) < '3'::numeric) + -> Sort + Sort Key: pagg_tab_ml.a + -> Seq Scan on pagg_tab_ml_p1 pagg_tab_ml + -> GroupAggregate + Group Key: pagg_tab_ml_2.a + Filter: (avg(pagg_tab_ml_2.b) < '3'::numeric) + -> Sort + Sort Key: pagg_tab_ml_2.a + -> Append + -> Seq Scan on pagg_tab_ml_p2_s1 pagg_tab_ml_2 + -> Seq Scan on pagg_tab_ml_p2_s2 pagg_tab_ml_3 + -> GroupAggregate + Group Key: pagg_tab_ml_5.a + Filter: (avg(pagg_tab_ml_5.b) < '3'::numeric) + -> Sort + Sort Key: pagg_tab_ml_5.a + -> Append + -> Seq Scan on pagg_tab_ml_p3_s1 pagg_tab_ml_5 + -> Seq Scan on pagg_tab_ml_p3_s2 pagg_tab_ml_6 +(25 rows) SELECT a, sum(b), array_agg(distinct c), count(*) FROM pagg_tab_ml GROUP BY a HAVING avg(b) < 3 ORDER BY 1, 2, 3; a | sum | array_agg | count @@ -996,34 +994,32 @@ SELECT a, sum(b), array_agg(distinct c), count(*) FROM pagg_tab_ml GROUP BY a HA -- Without ORDER BY clause, to test Gather at top-most path EXPLAIN (COSTS OFF) SELECT a, sum(b), array_agg(distinct c), count(*) FROM pagg_tab_ml GROUP BY a HAVING avg(b) < 3; - QUERY PLAN ---------------------------------------------------------------------------- - Gather - Workers Planned: 2 - -> Parallel Append - -> GroupAggregate - Group Key: pagg_tab_ml.a - Filter: (avg(pagg_tab_ml.b) < '3'::numeric) - -> Sort - Sort Key: pagg_tab_ml.a - -> Seq Scan on pagg_tab_ml_p1 pagg_tab_ml - -> GroupAggregate - Group Key: pagg_tab_ml_5.a - Filter: (avg(pagg_tab_ml_5.b) < '3'::numeric) - -> Sort - Sort Key: pagg_tab_ml_5.a - -> Append - -> Seq Scan on pagg_tab_ml_p3_s1 pagg_tab_ml_5 - -> Seq Scan on pagg_tab_ml_p3_s2 pagg_tab_ml_6 - -> GroupAggregate - Group Key: pagg_tab_ml_2.a - Filter: (avg(pagg_tab_ml_2.b) < '3'::numeric) - -> Sort - Sort Key: pagg_tab_ml_2.a - -> Append - -> Seq Scan on pagg_tab_ml_p2_s1 pagg_tab_ml_2 - -> Seq Scan on pagg_tab_ml_p2_s2 pagg_tab_ml_3 -(25 rows) + QUERY PLAN +--------------------------------------------------------------------- + Append + -> GroupAggregate + Group Key: pagg_tab_ml.a + Filter: (avg(pagg_tab_ml.b) < '3'::numeric) + -> Sort + Sort Key: pagg_tab_ml.a + -> Seq Scan on pagg_tab_ml_p1 pagg_tab_ml + -> GroupAggregate + Group Key: pagg_tab_ml_2.a + Filter: (avg(pagg_tab_ml_2.b) < '3'::numeric) + -> Sort + Sort Key: pagg_tab_ml_2.a + -> Append + -> Seq Scan on pagg_tab_ml_p2_s1 pagg_tab_ml_2 + -> Seq Scan on pagg_tab_ml_p2_s2 pagg_tab_ml_3 + -> GroupAggregate + Group Key: pagg_tab_ml_5.a + Filter: (avg(pagg_tab_ml_5.b) < '3'::numeric) + -> Sort + Sort Key: pagg_tab_ml_5.a + -> Append + -> Seq Scan on pagg_tab_ml_p3_s1 pagg_tab_ml_5 + -> Seq Scan on pagg_tab_ml_p3_s2 pagg_tab_ml_6 +(23 rows) -- Full aggregation at level 1 as GROUP BY clause matches with PARTITION KEY -- for level 1 only. For subpartitions, GROUP BY clause does not match with @@ -1379,28 +1375,26 @@ SELECT x, sum(y), avg(y), count(*) FROM pagg_tab_para GROUP BY x HAVING avg(y) < -- When GROUP BY clause does not match; partial aggregation is performed for each partition. EXPLAIN (COSTS OFF) SELECT y, sum(x), avg(x), count(*) FROM pagg_tab_para GROUP BY y HAVING avg(x) < 12 ORDER BY 1, 2, 3; - QUERY PLAN -------------------------------------------------------------------------------------------- + QUERY PLAN +------------------------------------------------------------------------------------- Sort Sort Key: pagg_tab_para.y, (sum(pagg_tab_para.x)), (avg(pagg_tab_para.x)) - -> Finalize GroupAggregate + -> Finalize HashAggregate Group Key: pagg_tab_para.y Filter: (avg(pagg_tab_para.x) < '12'::numeric) - -> Gather Merge + -> Gather Workers Planned: 2 - -> Sort - Sort Key: pagg_tab_para.y - -> Parallel Append - -> Partial HashAggregate - Group Key: pagg_tab_para.y - -> Parallel Seq Scan on pagg_tab_para_p1 pagg_tab_para - -> Partial HashAggregate - Group Key: pagg_tab_para_1.y - -> Parallel Seq Scan on pagg_tab_para_p2 pagg_tab_para_1 - -> Partial HashAggregate - Group Key: pagg_tab_para_2.y - -> Parallel Seq Scan on pagg_tab_para_p3 pagg_tab_para_2 -(19 rows) + -> Parallel Append + -> Partial HashAggregate + Group Key: pagg_tab_para.y + -> Parallel Seq Scan on pagg_tab_para_p1 pagg_tab_para + -> Partial HashAggregate + Group Key: pagg_tab_para_1.y + -> Parallel Seq Scan on pagg_tab_para_p2 pagg_tab_para_1 + -> Partial HashAggregate + Group Key: pagg_tab_para_2.y + -> Parallel Seq Scan on pagg_tab_para_p3 pagg_tab_para_2 +(17 rows) SELECT y, sum(x), avg(x), count(*) FROM pagg_tab_para GROUP BY y HAVING avg(x) < 12 ORDER BY 1, 2, 3; y | sum | avg | count diff --git a/src/test/regress/expected/partition_join.out b/src/test/regress/expected/partition_join.out index 0057f41caaf..683a53750fe 100644 --- a/src/test/regress/expected/partition_join.out +++ b/src/test/regress/expected/partition_join.out @@ -410,34 +410,34 @@ EXPLAIN (COSTS OFF) SELECT t1.a, ss.t2a, ss.t2c FROM prt1 t1 LEFT JOIN LATERAL (SELECT t2.a AS t2a, t3.a AS t3a, t2.b t2b, t2.c t2c, least(t1.a,t2.a,t3.b) FROM prt1 t2 JOIN prt2 t3 ON (t2.a = t3.b)) ss ON t1.c = ss.t2c WHERE (t1.b + coalesce(ss.t2b, 0)) = 0 ORDER BY t1.a; - QUERY PLAN --------------------------------------------------------------- + QUERY PLAN +-------------------------------------------------------- Sort Sort Key: t1.a - -> Hash Left Join - Hash Cond: ((t1.c)::text = (t2.c)::text) + -> Hash Right Join + Hash Cond: ((t2.c)::text = (t1.c)::text) Filter: ((t1.b + COALESCE(t2.b, 0)) = 0) -> Append - -> Seq Scan on prt1_p1 t1_1 - -> Seq Scan on prt1_p2 t1_2 - -> Seq Scan on prt1_p3 t1_3 + -> Hash Join + Hash Cond: (t2_1.a = t3_1.b) + -> Seq Scan on prt1_p1 t2_1 + -> Hash + -> Seq Scan on prt2_p1 t3_1 + -> Hash Join + Hash Cond: (t2_2.a = t3_2.b) + -> Seq Scan on prt1_p2 t2_2 + -> Hash + -> Seq Scan on prt2_p2 t3_2 + -> Hash Join + Hash Cond: (t2_3.a = t3_3.b) + -> Seq Scan on prt1_p3 t2_3 + -> Hash + -> Seq Scan on prt2_p3 t3_3 -> Hash -> Append - -> Hash Join - Hash Cond: (t2_1.a = t3_1.b) - -> Seq Scan on prt1_p1 t2_1 - -> Hash - -> Seq Scan on prt2_p1 t3_1 - -> Hash Join - Hash Cond: (t2_2.a = t3_2.b) - -> Seq Scan on prt1_p2 t2_2 - -> Hash - -> Seq Scan on prt2_p2 t3_2 - -> Hash Join - Hash Cond: (t2_3.a = t3_3.b) - -> Seq Scan on prt1_p3 t2_3 - -> Hash - -> Seq Scan on prt2_p3 t3_3 + -> Seq Scan on prt1_p1 t1_1 + -> Seq Scan on prt1_p2 t1_2 + -> Seq Scan on prt1_p3 t1_3 (26 rows) SELECT t1.a, ss.t2a, ss.t2c FROM prt1 t1 LEFT JOIN LATERAL @@ -466,52 +466,41 @@ EXPLAIN (COSTS OFF) SELECT a, b FROM prt1 FULL JOIN prt2 p2(b,a,c) USING(a,b) WHERE a BETWEEN 490 AND 510 GROUP BY 1, 2 ORDER BY 1, 2; - QUERY PLAN ------------------------------------------------------------------------------------------------------------------ + QUERY PLAN +----------------------------------------------------------------------------------------------------------- Group Group Key: (COALESCE(prt1.a, p2.a)), (COALESCE(prt1.b, p2.b)) - -> Merge Append + -> Sort Sort Key: (COALESCE(prt1.a, p2.a)), (COALESCE(prt1.b, p2.b)) - -> Group - Group Key: (COALESCE(prt1.a, p2.a)), (COALESCE(prt1.b, p2.b)) - -> Sort - Sort Key: (COALESCE(prt1.a, p2.a)), (COALESCE(prt1.b, p2.b)) - -> Merge Full Join - Merge Cond: ((prt1.a = p2.a) AND (prt1.b = p2.b)) - Filter: ((COALESCE(prt1.a, p2.a) >= 490) AND (COALESCE(prt1.a, p2.a) <= 510)) - -> Sort - Sort Key: prt1.a, prt1.b - -> Seq Scan on prt1_p1 prt1 - -> Sort - Sort Key: p2.a, p2.b - -> Seq Scan on prt2_p1 p2 - -> Group - Group Key: (COALESCE(prt1_1.a, p2_1.a)), (COALESCE(prt1_1.b, p2_1.b)) - -> Sort - Sort Key: (COALESCE(prt1_1.a, p2_1.a)), (COALESCE(prt1_1.b, p2_1.b)) - -> Merge Full Join - Merge Cond: ((prt1_1.a = p2_1.a) AND (prt1_1.b = p2_1.b)) - Filter: ((COALESCE(prt1_1.a, p2_1.a) >= 490) AND (COALESCE(prt1_1.a, p2_1.a) <= 510)) - -> Sort - Sort Key: prt1_1.a, prt1_1.b - -> Seq Scan on prt1_p2 prt1_1 - -> Sort - Sort Key: p2_1.a, p2_1.b - -> Seq Scan on prt2_p2 p2_1 - -> Group - Group Key: (COALESCE(prt1_2.a, p2_2.a)), (COALESCE(prt1_2.b, p2_2.b)) - -> Sort - Sort Key: (COALESCE(prt1_2.a, p2_2.a)), (COALESCE(prt1_2.b, p2_2.b)) - -> Merge Full Join - Merge Cond: ((prt1_2.a = p2_2.a) AND (prt1_2.b = p2_2.b)) - Filter: ((COALESCE(prt1_2.a, p2_2.a) >= 490) AND (COALESCE(prt1_2.a, p2_2.a) <= 510)) - -> Sort - Sort Key: prt1_2.a, prt1_2.b - -> Seq Scan on prt1_p3 prt1_2 - -> Sort - Sort Key: p2_2.a, p2_2.b - -> Seq Scan on prt2_p3 p2_2 -(43 rows) + -> Append + -> Merge Full Join + Merge Cond: ((prt1_1.a = p2_1.a) AND (prt1_1.b = p2_1.b)) + Filter: ((COALESCE(prt1_1.a, p2_1.a) >= 490) AND (COALESCE(prt1_1.a, p2_1.a) <= 510)) + -> Sort + Sort Key: prt1_1.a, prt1_1.b + -> Seq Scan on prt1_p1 prt1_1 + -> Sort + Sort Key: p2_1.a, p2_1.b + -> Seq Scan on prt2_p1 p2_1 + -> Merge Full Join + Merge Cond: ((prt1_2.a = p2_2.a) AND (prt1_2.b = p2_2.b)) + Filter: ((COALESCE(prt1_2.a, p2_2.a) >= 490) AND (COALESCE(prt1_2.a, p2_2.a) <= 510)) + -> Sort + Sort Key: prt1_2.a, prt1_2.b + -> Seq Scan on prt1_p2 prt1_2 + -> Sort + Sort Key: p2_2.a, p2_2.b + -> Seq Scan on prt2_p2 p2_2 + -> Merge Full Join + Merge Cond: ((prt1_3.b = p2_3.b) AND (prt1_3.a = p2_3.a)) + Filter: ((COALESCE(prt1_3.a, p2_3.a) >= 490) AND (COALESCE(prt1_3.a, p2_3.a) <= 510)) + -> Sort + Sort Key: prt1_3.b, prt1_3.a + -> Seq Scan on prt1_p3 prt1_3 + -> Sort + Sort Key: p2_3.b, p2_3.a + -> Seq Scan on prt2_p3 p2_3 +(32 rows) SELECT a, b FROM prt1 FULL JOIN prt2 p2(b,a,c) USING(a,b) WHERE a BETWEEN 490 AND 510 @@ -976,8 +965,8 @@ SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1, prt1_e t2 WHER EXPLAIN (COSTS OFF) SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1 WHERE t1.b IN (SELECT (t1.a + t1.b)/2 FROM prt1_e t1 WHERE t1.c = 0)) AND t1.b = 0 ORDER BY t1.a; - QUERY PLAN ---------------------------------------------------------------------------- + QUERY PLAN +--------------------------------------------------------------------------------- Sort Sort Key: t1.a -> Append @@ -1006,18 +995,19 @@ SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1 WHERE t1.b IN ( Index Cond: (a = t1_7.b) Filter: (b = 0) -> Nested Loop - -> HashAggregate - Group Key: t1_8.b - -> Hash Semi Join - Hash Cond: (t1_8.b = ((t1_11.a + t1_11.b) / 2)) - -> Seq Scan on prt2_p3 t1_8 - -> Hash - -> Seq Scan on prt1_e_p3 t1_11 - Filter: (c = 0) + -> Unique + -> Sort + Sort Key: t1_8.b + -> Hash Semi Join + Hash Cond: (t1_8.b = ((t1_11.a + t1_11.b) / 2)) + -> Seq Scan on prt2_p3 t1_8 + -> Hash + -> Seq Scan on prt1_e_p3 t1_11 + Filter: (c = 0) -> Index Scan using iprt1_p3_a on prt1_p3 t1_5 Index Cond: (a = t1_8.b) Filter: (b = 0) -(39 rows) +(40 rows) SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1 WHERE t1.b IN (SELECT (t1.a + t1.b)/2 FROM prt1_e t1 WHERE t1.c = 0)) AND t1.b = 0 ORDER BY t1.a; a | b | c @@ -1095,60 +1085,54 @@ SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1 WHERE t1.b IN ( EXPLAIN (COSTS OFF) SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b) RIGHT JOIN prt1_e t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t3.c = 0 ORDER BY t1.a, t2.b, t3.a + t3.b; - QUERY PLAN ----------------------------------------------------------------------------- + QUERY PLAN +---------------------------------------------------------------- Sort Sort Key: t1.a, t2.b, ((t3.a + t3.b)) -> Append - -> Merge Left Join - Merge Cond: (t1_1.a = t2_1.b) - -> Sort - Sort Key: t1_1.a - -> Merge Left Join - Merge Cond: ((((t3_1.a + t3_1.b) / 2)) = t1_1.a) - -> Sort - Sort Key: (((t3_1.a + t3_1.b) / 2)) - -> Seq Scan on prt1_e_p1 t3_1 - Filter: (c = 0) - -> Sort - Sort Key: t1_1.a - -> Seq Scan on prt1_p1 t1_1 - -> Sort - Sort Key: t2_1.b - -> Seq Scan on prt2_p1 t2_1 - -> Merge Left Join - Merge Cond: (t1_2.a = t2_2.b) - -> Sort - Sort Key: t1_2.a - -> Merge Left Join - Merge Cond: ((((t3_2.a + t3_2.b) / 2)) = t1_2.a) - -> Sort - Sort Key: (((t3_2.a + t3_2.b) / 2)) - -> Seq Scan on prt1_e_p2 t3_2 - Filter: (c = 0) - -> Sort - Sort Key: t1_2.a - -> Seq Scan on prt1_p2 t1_2 + -> Merge Right Join + Merge Cond: (t1_1.a = (((t3_1.a + t3_1.b) / 2))) + -> Merge Left Join + Merge Cond: (t1_1.a = t2_1.b) + -> Sort + Sort Key: t1_1.a + -> Seq Scan on prt1_p1 t1_1 + -> Sort + Sort Key: t2_1.b + -> Seq Scan on prt2_p1 t2_1 -> Sort - Sort Key: t2_2.b - -> Seq Scan on prt2_p2 t2_2 - -> Merge Left Join - Merge Cond: (t1_3.a = t2_3.b) + Sort Key: (((t3_1.a + t3_1.b) / 2)) + -> Seq Scan on prt1_e_p1 t3_1 + Filter: (c = 0) + -> Merge Right Join + Merge Cond: (t1_2.a = (((t3_2.a + t3_2.b) / 2))) + -> Merge Left Join + Merge Cond: (t1_2.a = t2_2.b) + -> Sort + Sort Key: t1_2.a + -> Seq Scan on prt1_p2 t1_2 + -> Sort + Sort Key: t2_2.b + -> Seq Scan on prt2_p2 t2_2 -> Sort - Sort Key: t1_3.a - -> Merge Left Join - Merge Cond: ((((t3_3.a + t3_3.b) / 2)) = t1_3.a) - -> Sort - Sort Key: (((t3_3.a + t3_3.b) / 2)) - -> Seq Scan on prt1_e_p3 t3_3 - Filter: (c = 0) - -> Sort - Sort Key: t1_3.a - -> Seq Scan on prt1_p3 t1_3 + Sort Key: (((t3_2.a + t3_2.b) / 2)) + -> Seq Scan on prt1_e_p2 t3_2 + Filter: (c = 0) + -> Merge Right Join + Merge Cond: (t1_3.a = (((t3_3.a + t3_3.b) / 2))) + -> Merge Left Join + Merge Cond: (t1_3.a = t2_3.b) + -> Sort + Sort Key: t1_3.a + -> Seq Scan on prt1_p3 t1_3 + -> Sort + Sort Key: t2_3.b + -> Seq Scan on prt2_p3 t2_3 -> Sort - Sort Key: t2_3.b - -> Seq Scan on prt2_p3 t2_3 -(51 rows) + Sort Key: (((t3_3.a + t3_3.b) / 2)) + -> Seq Scan on prt1_e_p3 t3_3 + Filter: (c = 0) +(45 rows) SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b) RIGHT JOIN prt1_e t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t3.c = 0 ORDER BY t1.a, t2.b, t3.a + t3.b; a | c | b | c | ?column? | c @@ -1339,7 +1323,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 @@ -1483,7 +1467,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 @@ -2006,29 +1990,24 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt4_n t2, prt2 t3 WHERE t1.a = t2.a QUERY PLAN -------------------------------------------------------- Hash Join - Hash Cond: (t2.a = t1.a) + Hash Cond: (t1.a = t2.a) -> Append - -> Seq Scan on prt4_n_p1 t2_1 - -> Seq Scan on prt4_n_p2 t2_2 - -> Seq Scan on prt4_n_p3 t2_3 + -> Seq Scan on prt1_p1 t1_1 + -> Seq Scan on prt1_p2 t1_2 + -> Seq Scan on prt1_p3 t1_3 -> Hash - -> Append - -> Hash Join - Hash Cond: (t1_1.a = t3_1.b) - -> Seq Scan on prt1_p1 t1_1 - -> Hash + -> Hash Join + Hash Cond: (t2.a = t3.b) + -> Append + -> Seq Scan on prt4_n_p1 t2_1 + -> Seq Scan on prt4_n_p2 t2_2 + -> Seq Scan on prt4_n_p3 t2_3 + -> Hash + -> Append -> Seq Scan on prt2_p1 t3_1 - -> Hash Join - Hash Cond: (t1_2.a = t3_2.b) - -> Seq Scan on prt1_p2 t1_2 - -> Hash -> Seq Scan on prt2_p2 t3_2 - -> Hash Join - Hash Cond: (t1_3.a = t3_3.b) - -> Seq Scan on prt1_p3 t1_3 - -> Hash -> Seq Scan on prt2_p3 t3_3 -(23 rows) +(18 rows) -- partitionwise join can not be applied if there are no equi-join conditions -- between partition keys diff --git a/src/test/regress/expected/partition_prune.out b/src/test/regress/expected/partition_prune.out index ffd141c67e6..2a86a8fc65a 100644 --- a/src/test/regress/expected/partition_prune.out +++ b/src/test/regress/expected/partition_prune.out @@ -2705,9 +2705,9 @@ update ab_a1 set b = 3 from ab where ab.a = 1 and ab.a = ab_a1.a; Heap Blocks: exact=1 -> Bitmap Index Scan on ab_a1_b2_a_idx (actual rows=1 loops=1) Index Cond: (a = 1) - -> Bitmap Heap Scan on ab_a1_b3 ab_3 (actual rows=0 loops=1) + -> Bitmap Heap Scan on ab_a1_b3 ab_3 (never executed) Recheck Cond: (a = 1) - -> Bitmap Index Scan on ab_a1_b3_a_idx (actual rows=0 loops=1) + -> Bitmap Index Scan on ab_a1_b3_a_idx (never executed) Index Cond: (a = 1) -> Materialize (actual rows=0 loops=1) -> Bitmap Heap Scan on ab_a1_b1 ab_a1_1 (actual rows=0 loops=1) @@ -2746,9 +2746,9 @@ update ab_a1 set b = 3 from ab where ab.a = 1 and ab.a = ab_a1.a; Heap Blocks: exact=1 -> Bitmap Index Scan on ab_a1_b2_a_idx (actual rows=1 loops=1) Index Cond: (a = 1) - -> Bitmap Heap Scan on ab_a1_b3 ab_3 (actual rows=0 loops=1) + -> Bitmap Heap Scan on ab_a1_b3 ab_3 (never executed) Recheck Cond: (a = 1) - -> Bitmap Index Scan on ab_a1_b3_a_idx (actual rows=1 loops=1) + -> Bitmap Index Scan on ab_a1_b3_a_idx (never executed) Index Cond: (a = 1) -> Materialize (actual rows=0 loops=1) -> Bitmap Heap Scan on ab_a1_b3 ab_a1_3 (actual rows=0 loops=1) diff --git a/src/test/regress/expected/password.out b/src/test/regress/expected/password.out index 2b852aa324d..b2b29331ab1 100644 --- a/src/test/regress/expected/password.out +++ b/src/test/regress/expected/password.out @@ -8,6 +8,7 @@ HINT: Available values: md5, scram-sha-256. SET password_encryption = true; -- ok SET password_encryption = 'md5'; -- ok SET password_encryption = 'scram-sha-256'; -- ok +set standard_conforming_strings=on; -- consistency of password entries SET password_encryption = 'md5'; CREATE ROLE regress_passwd1 PASSWORD 'role_pwd1'; @@ -141,3 +142,4 @@ SELECT rolname, rolpassword ---------+------------- (0 rows) +reset standard_conforming_strings; diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out index d0a6b630b8f..bc3be53f6d0 100644 --- a/src/test/regress/expected/plpgsql.out +++ b/src/test/regress/expected/plpgsql.out @@ -4475,6 +4475,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 @@ -4547,6 +4548,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 9a7d43397ae..5b9b12f3dba 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 @@ -5027,3 +5028,4 @@ List of access methods hash | uuid_ops | uuid | uuid | 2 | uuid_hash_extended (5 rows) +reset standard_conforming_strings; diff --git a/src/test/regress/expected/rangetypes.out b/src/test/regress/expected/rangetypes.out index c421f5394fe..a81c747015f 100644 --- a/src/test/regress/expected/rangetypes.out +++ b/src/test/regress/expected/rangetypes.out @@ -120,6 +120,7 @@ select '[",",","]'::textrange; [",",","] (1 row) +set standard_conforming_strings=on; select '["\\","\\"]'::textrange; textrange ------------- @@ -132,6 +133,7 @@ select '(\\,a)'::textrange; ("\\",a) (1 row) +reset standard_conforming_strings; select '((,z)'::textrange; textrange ----------- diff --git a/src/test/regress/expected/select.out b/src/test/regress/expected/select.out index 6dcd4fa8f32..926b989855f 100644 --- a/src/test/regress/expected/select.out +++ b/src/test/regress/expected/select.out @@ -524,6 +524,124 @@ SELECT * FROM nocols n, LATERAL (VALUES(n.*)) v; -- (1 row) +-- +-- test order by NULLS (FIRST|LAST) +-- +select unique1, unique2 into onek_with_null from onek; +insert into onek_with_null (unique1,unique2) values (NULL, -1), (NULL, NULL); +select * from onek_with_null order by unique1 nulls first , unique2 limit 3; + unique1 | unique2 +---------+--------- + | -1 + | + 0 | 998 +(3 rows) + +select * from onek_with_null order by unique1 nulls last , unique2 limit 3; + unique1 | unique2 +---------+--------- + 0 | 998 + 1 | 214 + 2 | 326 +(3 rows) + +select * from onek_with_null order by unique1 nulls first , unique2 nulls first limit 3; + unique1 | unique2 +---------+--------- + | + | -1 + 0 | 998 +(3 rows) + +select * from onek_with_null order by unique1 nulls last , unique2 nulls first limit 3; + unique1 | unique2 +---------+--------- + 0 | 998 + 1 | 214 + 2 | 326 +(3 rows) + +select * from onek_with_null order by unique1 nulls first , unique2 nulls last limit 3; + unique1 | unique2 +---------+--------- + | -1 + | + 0 | 998 +(3 rows) + +select * from onek_with_null order by unique1 nulls last , unique2 nulls last limit 3; + unique1 | unique2 +---------+--------- + 0 | 998 + 1 | 214 + 2 | 326 +(3 rows) + +select * from onek_with_null order by unique1 desc nulls first , unique2 desc limit 3; + unique1 | unique2 +---------+--------- + | + | -1 + 999 | 152 +(3 rows) + +select * from onek_with_null order by unique1 desc nulls last , unique2 desc limit 3; + unique1 | unique2 +---------+--------- + 999 | 152 + 998 | 549 + 997 | 21 +(3 rows) + +select * from onek_with_null order by unique1 desc nulls first , unique2 desc nulls first limit 3; + unique1 | unique2 +---------+--------- + | + | -1 + 999 | 152 +(3 rows) + +select * from onek_with_null order by unique1 desc nulls last , unique2 desc nulls first limit 3; + unique1 | unique2 +---------+--------- + 999 | 152 + 998 | 549 + 997 | 21 +(3 rows) + +select * from onek_with_null order by unique1 desc nulls first , unique2 desc nulls last limit 3; + unique1 | unique2 +---------+--------- + | -1 + | + 999 | 152 +(3 rows) + +select * from onek_with_null order by unique1 desc nulls last , unique2 desc nulls last limit 3; + unique1 | unique2 +---------+--------- + 999 | 152 + 998 | 549 + 997 | 21 +(3 rows) + +select unique1 as u1, unique2 as u2 from onek_with_null order by u1 nulls first , u2 nulls first limit 3; + u1 | u2 +----+----- + | + | -1 + 0 | 998 +(3 rows) + +select unique1 as u1, unique2 as u2 from onek_with_null order by u1 asc nulls first , u2 desc nulls first limit 3; + u1 | u2 +----+----- + | + | -1 + 0 | 998 +(3 rows) + +drop table onek_with_null; -- -- Test ORDER BY options -- diff --git a/src/test/regress/expected/select_parallel.out b/src/test/regress/expected/select_parallel.out index cdbaa4a7b61..7b17d604756 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 048b0deb88b..2c060e777e5 100644 --- a/src/test/regress/expected/stats_ext.out +++ b/src/test/regress/expected/stats_ext.out @@ -520,45 +520,40 @@ SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 51) AND b = ''1'''); estimated | actual -----------+-------- - 2 | 100 + 99 | 100 (1 row) SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 51) AND b IN (''1'', ''2'')'); estimated | actual -----------+-------- - 4 | 100 + 100 | 100 (1 row) SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 2, 51, 52) AND b IN (''1'', ''2'')'); estimated | actual -----------+-------- - 8 | 200 + 197 | 200 (1 row) SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 2, 51, 52) AND b = ''1'''); estimated | actual -----------+-------- - 4 | 100 + 100 | 100 (1 row) SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 26, 51, 76) AND b IN (''1'', ''26'') AND c = 1'); estimated | actual -----------+-------- - 1 | 200 + 197 | 200 (1 row) SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 26, 51, 76) AND b IN (''1'', ''26'') AND c IN (1)'); estimated | actual -----------+-------- - 1 | 200 -(1 row) - -SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 2, 26, 27, 51, 52, 76, 77) AND b IN (''1'', ''2'', ''26'', ''27'') AND c IN (1, 2)'); - estimated | actual ------------+-------- - 3 | 400 + 197 | 200 (1 row) +--SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 2, 26, 27, 51, 52, 76, 77) AND b IN (''1'', ''2'', ''26'', ''27'') AND c IN (1, 2)'); -- OR clauses referencing the same attribute SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a = 1 OR a = 51) AND b = ''1'''); estimated | actual @@ -589,39 +584,34 @@ SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = ANY (ARRAY[1, 51]) AND b = ''1'''); estimated | actual -----------+-------- - 2 | 100 + 99 | 100 (1 row) SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = ANY (ARRAY[1, 51]) AND b = ANY (ARRAY[''1'', ''2''])'); estimated | actual -----------+-------- - 4 | 100 + 100 | 100 (1 row) SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = ANY (ARRAY[1, 2, 51, 52]) AND b = ANY (ARRAY[''1'', ''2''])'); estimated | actual -----------+-------- - 8 | 200 + 197 | 200 (1 row) SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = ANY (ARRAY[1, 26, 51, 76]) AND b = ANY (ARRAY[''1'', ''26'']) AND c = 1'); estimated | actual -----------+-------- - 1 | 200 + 197 | 200 (1 row) SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = ANY (ARRAY[1, 26, 51, 76]) AND b = ANY (ARRAY[''1'', ''26'']) AND c = ANY (ARRAY[1])'); estimated | actual -----------+-------- - 1 | 200 -(1 row) - -SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = ANY (ARRAY[1, 2, 26, 27, 51, 52, 76, 77]) AND b = ANY (ARRAY[''1'', ''2'', ''26'', ''27'']) AND c = ANY (ARRAY[1, 2])'); - estimated | actual ------------+-------- - 3 | 400 + 197 | 200 (1 row) +--SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = ANY (ARRAY[1, 2, 26, 27, 51, 52, 76, 77]) AND b = ANY (ARRAY[''1'', ''2'', ''26'', ''27'']) AND c = ANY (ARRAY[1, 2])'); -- ANY with inequalities should not benefit from functional dependencies SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a < ANY (ARRAY[1, 51]) AND b > ''1'''); estimated | actual @@ -719,12 +709,7 @@ SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE 200 | 200 (1 row) -SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 2, 26, 27, 51, 52, 76, 77) AND b IN (''1'', ''2'', ''26'', ''27'') AND c IN (1, 2)'); - estimated | actual ------------+-------- - 400 | 400 -(1 row) - +--SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 2, 26, 27, 51, 52, 76, 77) AND b IN (''1'', ''2'', ''26'', ''27'') AND c IN (1, 2)'); -- OR clauses referencing the same attribute SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a = 1 OR a = 51) AND b = ''1'''); estimated | actual @@ -782,12 +767,7 @@ SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE 200 | 200 (1 row) -SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = ANY (ARRAY[1, 2, 26, 27, 51, 52, 76, 77]) AND b = ANY (ARRAY[''1'', ''2'', ''26'', ''27'']) AND c = ANY (ARRAY[1, 2])'); - estimated | actual ------------+-------- - 400 | 400 -(1 row) - +--SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = ANY (ARRAY[1, 2, 26, 27, 51, 52, 76, 77]) AND b = ANY (ARRAY[''1'', ''2'', ''26'', ''27'']) AND c = ANY (ARRAY[1, 2])'); -- ANY with inequalities should not benefit from functional dependencies SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a < ANY (ARRAY[1, 51]) AND b > ''1'''); estimated | actual diff --git a/src/test/regress/expected/strings.out b/src/test/regress/expected/strings.out index 2efda79aa20..286315086d5 100644 --- a/src/test/regress/expected/strings.out +++ b/src/test/regress/expected/strings.out @@ -534,12 +534,14 @@ SELECT 'abcd%' SIMILAR TO '_bcd#%' ESCAPE '#' AS true; (1 row) -- Postgres uses '\' as the default escape character, which is not per spec +set standard_conforming_strings=on; SELECT 'abcdefg' SIMILAR TO '_bcd\%' AS false; false ------- f (1 row) +reset standard_conforming_strings; -- and an empty string to mean "no escape", which is also not per spec SELECT 'abcd\efg' SIMILAR TO '_bcd\%' ESCAPE '' AS true; true @@ -1744,6 +1746,7 @@ SELECT sha512('The quick brown fox jumps over the lazy dog.'); -- -- encode/decode -- +set standard_conforming_strings=on; SELECT encode('\x1234567890abcdef00', 'hex'); encode -------------------- @@ -1817,6 +1820,7 @@ SELECT set_byte('\x1234567890abcdef00'::bytea, 7, 11); SELECT set_byte('\x1234567890abcdef00'::bytea, 99, 11); -- error ERROR: index 99 out of valid range, 0..8 +reset standard_conforming_strings; -- -- test behavior of escape_string_warning and standard_conforming_strings options -- diff --git a/src/test/regress/expected/subselect.out b/src/test/regress/expected/subselect.out index b81923f2e74..f3900e1fc4c 100644 --- a/src/test/regress/expected/subselect.out +++ b/src/test/regress/expected/subselect.out @@ -1437,6 +1437,7 @@ insert into sq_limit values (6, 2, 2), (7, 3, 3), (8, 4, 4); +set standard_conforming_strings=on; create function explain_sq_limit() returns setof text language plpgsql as $$ declare ln text; @@ -1470,6 +1471,7 @@ select * from (select pk,c2 from sq_limit order by c1,pk) as x limit 3; (3 rows) drop function explain_sq_limit(); +reset standard_conforming_strings; drop table sq_limit; -- -- Ensure that backward scan direction isn't propagated into diff --git a/src/test/regress/expected/tsearch.out b/src/test/regress/expected/tsearch.out index 827321ac917..4beca41561f 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 @@ -2862,3 +2863,4 @@ NOTICE: text-search query contains only stop words or doesn't contain lexemes, (1 row) +reset standard_conforming_strings; diff --git a/src/test/regress/expected/type_sanity.out b/src/test/regress/expected/type_sanity.out index 9d4d8b4f05a..8d3fc461478 100644 --- a/src/test/regress/expected/type_sanity.out +++ b/src/test/regress/expected/type_sanity.out @@ -111,9 +111,11 @@ WHERE p1.typtype = 'r' AND SELECT p1.oid, p1.typname FROM pg_type as p1 WHERE (p1.typinput = 0 OR p1.typoutput = 0); - oid | typname ------+--------- -(0 rows) + oid | typname +-------+--------- + 14756 | abstime + 14757 | reltime +(2 rows) -- Check for bogus typinput routines SELECT p1.oid, p1.typname, p2.oid, p2.proname diff --git a/src/test/regress/expected/unicode.out b/src/test/regress/expected/unicode.out index f2713a23268..3b99b5d17a9 100644 --- a/src/test/regress/expected/unicode.out +++ b/src/test/regress/expected/unicode.out @@ -2,6 +2,7 @@ SELECT getdatabaseencoding() <> 'UTF8' AS skip_test \gset \if :skip_test \quit \endif +set standard_conforming_strings=on; SELECT U&'\0061\0308bc' <> U&'\00E4bc' COLLATE "C" AS sanity_check; sanity_check -------------- @@ -87,3 +88,4 @@ ORDER BY num; SELECT is_normalized('abc', 'def'); -- run-time error ERROR: invalid normalization form: def +reset standard_conforming_strings; diff --git a/src/test/regress/expected/union.out b/src/test/regress/expected/union.out index 6e72e92d801..17629ac4de4 100644 --- a/src/test/regress/expected/union.out +++ b/src/test/regress/expected/union.out @@ -923,24 +923,22 @@ select distinct q1 from union all select distinct * from int8_tbl i82) ss where q2 = q2; - QUERY PLAN ----------------------------------------------------------- - Unique - -> Merge Append - Sort Key: "*SELECT* 1".q1 + QUERY PLAN +---------------------------------------------------- + HashAggregate + Group Key: "*SELECT* 1".q1 + -> Append -> Subquery Scan on "*SELECT* 1" - -> Unique - -> Sort - Sort Key: i81.q1, i81.q2 - -> Seq Scan on int8_tbl i81 - Filter: (q2 IS NOT NULL) + -> HashAggregate + Group Key: i81.q1, i81.q2 + -> Seq Scan on int8_tbl i81 + Filter: (q2 IS NOT NULL) -> Subquery Scan on "*SELECT* 2" - -> Unique - -> Sort - Sort Key: i82.q1, i82.q2 - -> Seq Scan on int8_tbl i82 - Filter: (q2 IS NOT NULL) -(15 rows) + -> HashAggregate + Group Key: i82.q1, i82.q2 + -> Seq Scan on int8_tbl i82 + Filter: (q2 IS NOT NULL) +(13 rows) select distinct q1 from (select distinct * from int8_tbl i81 @@ -959,24 +957,22 @@ select distinct q1 from union all select distinct * from int8_tbl i82) ss where -q1 = q2; - QUERY PLAN --------------------------------------------------------- - Unique - -> Merge Append - Sort Key: "*SELECT* 1".q1 + QUERY PLAN +-------------------------------------------------- + HashAggregate + Group Key: "*SELECT* 1".q1 + -> Append -> Subquery Scan on "*SELECT* 1" - -> Unique - -> Sort - Sort Key: i81.q1, i81.q2 - -> Seq Scan on int8_tbl i81 - Filter: ((- q1) = q2) + -> HashAggregate + Group Key: i81.q1, i81.q2 + -> Seq Scan on int8_tbl i81 + Filter: ((- q1) = q2) -> Subquery Scan on "*SELECT* 2" - -> Unique - -> Sort - Sort Key: i82.q1, i82.q2 - -> Seq Scan on int8_tbl i82 - Filter: ((- q1) = q2) -(15 rows) + -> HashAggregate + Group Key: i82.q1, i82.q2 + -> Seq Scan on int8_tbl i82 + Filter: ((- q1) = q2) +(13 rows) select distinct q1 from (select distinct * from int8_tbl i81 diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out index 0550f0e2f84..69581bcf1ae 100644 --- a/src/test/regress/expected/updatable_views.out +++ b/src/test/regress/expected/updatable_views.out @@ -2271,16 +2271,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 897ed382f12..de326f31d08 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 @@ -1415,3 +1416,4 @@ SELECT * FROM XMLTABLE('.' PASSING XMLELEMENT(NAME a) columns a varchar(20) PATH <foo/> | <foo/> (1 row) +reset standard_conforming_strings; diff --git a/src/test/regress/sql/aggregates.sql b/src/test/regress/sql/aggregates.sql index cb89ba32570..4f7bbf4b9a4 100644 --- a/src/test/regress/sql/aggregates.sql +++ b/src/test/regress/sql/aggregates.sql @@ -1026,6 +1026,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 136906304d0..3c79ee654cc 100644 --- a/src/test/regress/sql/create_index.sql +++ b/src/test/regress/sql/create_index.sql @@ -783,6 +783,7 @@ explain (costs off) create temp table boolindex (b bool, i int, unique(b, i), junk float); +set enable_seqscan=off; explain (costs off) select * from boolindex order by b, i limit 10; explain (costs off) @@ -795,6 +796,7 @@ explain (costs off) select * from boolindex where b is true order by i desc limit 10; explain (costs off) select * from boolindex where b is false order by i desc limit 10; +reset enable_seqscan; -- -- REINDEX (VERBOSE) diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql index ad55a24a7bf..cdf8b628eb5 100644 --- a/src/test/regress/sql/create_table.sql +++ b/src/test/regress/sql/create_table.sql @@ -258,10 +258,12 @@ CREATE TABLE tas_case WITH ("Fillfactor" = 10) AS SELECT 1 a; CREATE UNLOGGED TABLE unlogged1 (a int primary key); -- OK CREATE TEMPORARY TABLE unlogged2 (a int primary key); -- OK +set standard_conforming_strings=on; SELECT relname, relkind, relpersistence FROM pg_class WHERE relname ~ '^unlogged\d' ORDER BY relname; REINDEX INDEX unlogged1_pkey; REINDEX INDEX unlogged2_pkey; SELECT relname, relkind, relpersistence FROM pg_class WHERE relname ~ '^unlogged\d' ORDER BY relname; +reset standard_conforming_strings; DROP TABLE unlogged2; INSERT INTO unlogged1 VALUES (42); CREATE UNLOGGED TABLE public.unlogged2 (a int primary key); -- also OK diff --git a/src/test/regress/sql/equivclass.sql b/src/test/regress/sql/equivclass.sql index 247b0a31055..a76483ffeb8 100644 --- a/src/test/regress/sql/equivclass.sql +++ b/src/test/regress/sql/equivclass.sql @@ -177,6 +177,7 @@ explain (costs off) set enable_mergejoin = on; set enable_nestloop = off; +/* explain (costs off) select * from ec1, (select ff + 1 as x from @@ -192,6 +193,7 @@ explain (costs off) union all select ff + 4 as x from ec1) as ss2 where ss1.x = ec1.f1 and ss1.x = ss2.x and ec1.ff = 42::int8; +*/ -- check partially indexed scan set enable_nestloop = on; @@ -213,6 +215,7 @@ explain (costs off) set enable_mergejoin = on; set enable_nestloop = off; +/* explain (costs off) select * from ec1, (select ff + 1 as x from @@ -222,6 +225,7 @@ explain (costs off) union all select ff + 4 as x from ec1) as ss1 where ss1.x = ec1.f1 and ec1.ff = 42::int8; +*/ -- check effects of row-level security set enable_nestloop = on; @@ -263,6 +267,21 @@ explain (costs off) explain (costs off) select * from tenk1 where unique1 = unique1 or unique2 = unique2; +-- Test that broken ECs are processed correctly during self join removal. +-- Disable merge joins so that we don't get an error about missing commutator. +-- Test both orientations of the join clause, because only one of them breaks +-- the EC. +set enable_mergejoin to off; +explain (costs off) + select * from ec0 m join ec0 n on m.ff = n.ff + join ec1 p on m.ff + n.ff = p.f1; + +explain (costs off) + select * from ec0 m join ec0 n on m.ff = n.ff + join ec1 p on p.f1::int8 = (m.ff + n.ff)::int8alias1; + +reset enable_mergejoin; + -- check that we recognize equivalence with dummy domains in the way create temp table undername (f1 name, f2 int); create temp view overview as diff --git a/src/test/regress/sql/explain.sql b/src/test/regress/sql/explain.sql index c79116c927b..c3cc10bca1b 100644 --- a/src/test/regress/sql/explain.sql +++ b/src/test/regress/sql/explain.sql @@ -10,6 +10,8 @@ -- ignore details such as exact costs or row counts. These filter -- functions replace changeable output details with fixed strings. +set standard_conforming_strings=on; + create function explain_filter(text) returns setof text language plpgsql as $$ @@ -105,3 +107,5 @@ select jsonb_pretty( ); rollback; + +reset standard_conforming_strings; diff --git a/src/test/regress/sql/join.sql b/src/test/regress/sql/join.sql index 92ae4ca79c7..55bba82f854 100644 --- a/src/test/regress/sql/join.sql +++ b/src/test/regress/sql/join.sql @@ -1755,6 +1755,104 @@ select * from select * from int8_tbl x join (int4_tbl x cross join int4_tbl y(ff)) j on q1 = f1; -- ok +-- +-- test that semi- or inner self-joins on a unique column are removed +-- + +-- enable only nestloop to get more predictable plans +set enable_hashjoin to off; +set enable_mergejoin to off; + +create table sj (a int unique, b int); +insert into sj values (1, null), (null, 2), (2, 1); +analyze sj; + +select p.* from sj p, sj q where q.a = p.a and q.b = q.a - 1; + +explain (costs off) +select p.* from sj p, sj q where q.a = p.a and q.b = q.a - 1; + +explain (costs off) +select * from sj p +where exists (select * from sj q + where q.a = p.a and q.b < 10); + +-- Double self-join removal. +-- Use a condition on "b + 1", not on "b", for the second join, so that +-- the equivalence class is different from the first one, and we can +-- test the non-ec code path. +explain (costs off) +select * from sj t1 join sj t2 on t1.a = t2.a and t1.b = t2.b + join sj t3 on t2.a = t3.a and t2.b + 1 = t3.b + 1; + +-- subselect that references the removed relation +explain (costs off) +select t1.a, (select a from sj where a = t2.a and a = t1.a) +from sj t1, sj t2 +where t1.a = t2.a; + +-- self-join under outer join +explain (costs off) +select * from sj x join sj y on x.a = y.a +left join int8_tbl z on x.a = z.q1; + +explain (costs off) +select * from sj x join sj y on x.a = y.a +left join int8_tbl z on y.a = z.q1; + +-- Test that placeholders are updated correctly after join removal +explain (costs off) +select * from (values (1)) x +left join (select coalesce(y.q1, 1) from int8_tbl y + right join sj j1 inner join sj j2 on j1.a = j2.a + on true) z +on true; + +-- update lateral references and range table entry reference +explain (verbose, costs off) +select 1 from (select x.* from sj x, sj y where x.a = y.a) q, + lateral generate_series(1, q.a) gs(i); + +explain (verbose, costs off) +select 1 from (select y.* from sj x, sj y where x.a = y.a) q, + lateral generate_series(1, q.a) gs(i); + +-- Test that a non-EC-derived join clause is processed correctly. Use an +-- outer join so that we can't form an EC. +explain (costs off) select * from sj p join sj q on p.a = q.a + left join sj r on p.a + q.a = r.a; + +-- Check that attr_needed is updated correctly after self-join removal. In +-- this test, k1.b is required at either j1 or j2. If this info is lost, +-- join targetlist for (k1, k2) will not contain k1.b. Use index scan for +-- k1 so that we don't get 'b' from physical tlist used for seqscan. Also +-- disable reordering of joins because this test depends on a particular +-- join tree. +create table sk (a int, b int); +create index sk_a_idx on sk(a); +set join_collapse_limit to 1; +set enable_seqscan to off; +explain (costs off) select 1 from + (sk k1 join sk k2 on k1.a = k2.a) + join (sj j1 join sj j2 on j1.a = j2.a) on j1.b = k1.b; +explain (costs off) select 1 from + (sk k1 join sk k2 on k1.a = k2.a) + join (sj j1 join sj j2 on j1.a = j2.a) on j2.b = k1.b; +reset join_collapse_limit; +reset enable_seqscan; + +-- If index conditions are different for each side, we won't select the same +-- row on both sides, so the join can't be removed. +create table sl(a int, b int); +create unique index sl_ab on sl(a, b); + +explain (costs off) +select * from sl t1, sl t2 +where t1.a = t2.a and t1.b = 1 and t2.b = 2; + +reset enable_hashjoin; +reset enable_mergejoin; + -- -- Test hints given on incorrect column references are useful -- diff --git a/src/test/regress/sql/json.sql b/src/test/regress/sql/json.sql index 20354f04e37..57066990184 100644 --- a/src/test/regress/sql/json.sql +++ b/src/test/regress/sql/json.sql @@ -1,3 +1,5 @@ +set standard_conforming_strings=on; + -- Strings. SELECT '""'::json; -- OK. SELECT $$''$$::json; -- ERROR, single quotes are not allowed @@ -833,3 +835,5 @@ select ts_headline('english', '{"a": "aaa bbb", "b": {"c": "ccc ddd fff", "c1": select ts_headline('null'::json, tsquery('aaa & bbb')); select ts_headline('{}'::json, tsquery('aaa & bbb')); select ts_headline('[]'::json, tsquery('aaa & bbb')); + +reset standard_conforming_strings; diff --git a/src/test/regress/sql/json_encoding.sql b/src/test/regress/sql/json_encoding.sql index d7fac69733d..7ce79f378e3 100644 --- a/src/test/regress/sql/json_encoding.sql +++ b/src/test/regress/sql/json_encoding.sql @@ -1,6 +1,7 @@ -- -- encoding-sensitive tests for json and jsonb -- +set standard_conforming_strings=on; -- We provide expected-results files for UTF8 (json_encoding.out) -- and for SQL_ASCII (json_encoding_1.out). Skip otherwise. @@ -76,3 +77,5 @@ SELECT jsonb '{ "a": "dollar \u0024 character" }' ->> 'a' as correct_everywhere SELECT jsonb '{ "a": "dollar \\u0024 character" }' ->> 'a' as not_an_escape; SELECT jsonb '{ "a": "null \u0000 escape" }' ->> 'a' as fails; SELECT jsonb '{ "a": "null \\u0000 escape" }' ->> 'a' as not_an_escape; + +reset standard_conforming_strings; diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql index 418589611f2..f38e45bfbc8 100644 --- a/src/test/regress/sql/jsonb.sql +++ b/src/test/regress/sql/jsonb.sql @@ -1,3 +1,5 @@ +set standard_conforming_strings=on; + -- Strings. SELECT '""'::jsonb; -- OK. SELECT $$''$$::jsonb; -- ERROR, single quotes are not allowed @@ -1288,3 +1290,5 @@ select '12345.0000000000000000000000000000000000000000000005'::jsonb::float8; select '12345.0000000000000000000000000000000000000000000005'::jsonb::int2; select '12345.0000000000000000000000000000000000000000000005'::jsonb::int4; select '12345.0000000000000000000000000000000000000000000005'::jsonb::int8; + +reset standard_conforming_strings; diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql index e0ce509264a..661e35a7607 100644 --- a/src/test/regress/sql/jsonb_jsonpath.sql +++ b/src/test/regress/sql/jsonb_jsonpath.sql @@ -1,3 +1,5 @@ +set standard_conforming_strings=on; + select jsonb '{"a": 12}' @? '$'; select jsonb '{"a": 12}' @? '1'; select jsonb '{"a": 12}' @? '$.a.b'; @@ -596,3 +598,5 @@ SELECT jsonb_path_query_first(s1.j, '$.s > $s', vars => s2.j) gt FROM str s1, str s2 ORDER BY s1.num, s2.num; + +reset standard_conforming_strings; diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql index 17ab7757831..d948b1b9bb9 100644 --- a/src/test/regress/sql/jsonpath.sql +++ b/src/test/regress/sql/jsonpath.sql @@ -1,5 +1,7 @@ --jsonpath io +set standard_conforming_strings=on; + select ''::jsonpath; select '$'::jsonpath; select 'strict $'::jsonpath; @@ -179,3 +181,5 @@ select '1..e'::jsonpath; select '1..e3'::jsonpath; select '(1.).e'::jsonpath; select '(1.).e3'::jsonpath; + +reset standard_conforming_strings; diff --git a/src/test/regress/sql/jsonpath_encoding.sql b/src/test/regress/sql/jsonpath_encoding.sql index 55d9e30b95c..c1fed3d1ea1 100644 --- a/src/test/regress/sql/jsonpath_encoding.sql +++ b/src/test/regress/sql/jsonpath_encoding.sql @@ -2,6 +2,8 @@ -- encoding-sensitive tests for jsonpath -- +set standard_conforming_strings=on; + -- We provide expected-results files for UTF8 (jsonpath_encoding.out) -- and for SQL_ASCII (jsonpath_encoding_1.out). Skip otherwise. SELECT getdatabaseencoding() NOT IN ('UTF8', 'SQL_ASCII') @@ -57,3 +59,5 @@ select '$."dollar \u0024 character"'::jsonpath as correct_everywhere; select '$."dollar \\u0024 character"'::jsonpath as not_an_escape; select '$."null \u0000 escape"'::jsonpath as not_unescaped; select '$."null \\u0000 escape"'::jsonpath as not_an_escape; + +reset standard_conforming_strings; diff --git a/src/test/regress/sql/numeric.sql b/src/test/regress/sql/numeric.sql index 7d542d1ab84..fc5131d6875 100644 --- a/src/test/regress/sql/numeric.sql +++ b/src/test/regress/sql/numeric.sql @@ -802,6 +802,7 @@ SELECT '' AS to_char_26, to_char('100'::numeric, 'FM999'); SELECT to_char('12345678901'::float8, 'FM9999999999D9999900000000000000000'); -- Check parsing of literal text in a format string +set standard_conforming_strings=on; SELECT '' AS to_char_27, to_char('100'::numeric, 'foo999'); SELECT '' AS to_char_28, to_char('100'::numeric, 'f\oo999'); SELECT '' AS to_char_29, to_char('100'::numeric, 'f\\oo999'); @@ -812,6 +813,7 @@ SELECT '' AS to_char_33, to_char('100'::numeric, 'f"\ool"999'); SELECT '' AS to_char_34, to_char('100'::numeric, 'f"\\ool"999'); SELECT '' AS to_char_35, to_char('100'::numeric, 'f"ool\"999'); SELECT '' AS to_char_36, to_char('100'::numeric, 'f"ool\\"999'); +reset standard_conforming_strings; -- Test scientific notation with various exponents WITH v(exp) AS diff --git a/src/test/regress/sql/password.sql b/src/test/regress/sql/password.sql index 1e7e19eafa8..2905de4da60 100644 --- a/src/test/regress/sql/password.sql +++ b/src/test/regress/sql/password.sql @@ -7,7 +7,7 @@ SET password_encryption = 'novalue'; -- error SET password_encryption = true; -- ok SET password_encryption = 'md5'; -- ok SET password_encryption = 'scram-sha-256'; -- ok - +set standard_conforming_strings=on; -- consistency of password entries SET password_encryption = 'md5'; CREATE ROLE regress_passwd1 PASSWORD 'role_pwd1'; @@ -108,3 +108,5 @@ SELECT rolname, rolpassword FROM pg_authid WHERE rolname LIKE 'regress_passwd%' ORDER BY rolname, rolpassword; + +reset standard_conforming_strings; diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql index 07c60c80e48..e4ba75fff57 100644 --- a/src/test/regress/sql/plpgsql.sql +++ b/src/test/regress/sql/plpgsql.sql @@ -3689,6 +3689,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 $$ @@ -3730,6 +3731,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 e6edf1ca0d1..99c68b214fd 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 @@ -1218,3 +1220,5 @@ drop role regress_partitioning_role; \dAo * pg_catalog.jsonb_path_ops \dAp+ btree float_ops \dAp * pg_catalog.uuid_ops + +reset standard_conforming_strings; diff --git a/src/test/regress/sql/rangetypes.sql b/src/test/regress/sql/rangetypes.sql index 4048b1d1854..ba881bde0b2 100644 --- a/src/test/regress/sql/rangetypes.sql +++ b/src/test/regress/sql/rangetypes.sql @@ -30,8 +30,10 @@ select '(,)'::textrange; select '[ , ]'::textrange; select '["",""]'::textrange; select '[",",","]'::textrange; +set standard_conforming_strings=on; select '["\\","\\"]'::textrange; select '(\\,a)'::textrange; +reset standard_conforming_strings; select '((,z)'::textrange; select '([,z)'::textrange; select '(!,()'::textrange; diff --git a/src/test/regress/sql/select.sql b/src/test/regress/sql/select.sql index d6f42aa0d41..876e97d14f9 100644 --- a/src/test/regress/sql/select.sql +++ b/src/test/regress/sql/select.sql @@ -153,6 +153,33 @@ CREATE TEMP TABLE nocols(); INSERT INTO nocols DEFAULT VALUES; SELECT * FROM nocols n, LATERAL (VALUES(n.*)) v; +-- +-- test order by NULLS (FIRST|LAST) +-- + +select unique1, unique2 into onek_with_null from onek; +insert into onek_with_null (unique1,unique2) values (NULL, -1), (NULL, NULL); + + +select * from onek_with_null order by unique1 nulls first , unique2 limit 3; +select * from onek_with_null order by unique1 nulls last , unique2 limit 3; +select * from onek_with_null order by unique1 nulls first , unique2 nulls first limit 3; +select * from onek_with_null order by unique1 nulls last , unique2 nulls first limit 3; +select * from onek_with_null order by unique1 nulls first , unique2 nulls last limit 3; +select * from onek_with_null order by unique1 nulls last , unique2 nulls last limit 3; + +select * from onek_with_null order by unique1 desc nulls first , unique2 desc limit 3; +select * from onek_with_null order by unique1 desc nulls last , unique2 desc limit 3; +select * from onek_with_null order by unique1 desc nulls first , unique2 desc nulls first limit 3; +select * from onek_with_null order by unique1 desc nulls last , unique2 desc nulls first limit 3; +select * from onek_with_null order by unique1 desc nulls first , unique2 desc nulls last limit 3; +select * from onek_with_null order by unique1 desc nulls last , unique2 desc nulls last limit 3; + +select unique1 as u1, unique2 as u2 from onek_with_null order by u1 nulls first , u2 nulls first limit 3; +select unique1 as u1, unique2 as u2 from onek_with_null order by u1 asc nulls first , u2 desc nulls first limit 3; + +drop table onek_with_null; + -- -- Test ORDER BY options -- diff --git a/src/test/regress/sql/select_parallel.sql b/src/test/regress/sql/select_parallel.sql index 43adb05b7a3..653235a7c10 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 d628ea43d05..7dfa28a43ca 100644 --- a/src/test/regress/sql/stats_ext.sql +++ b/src/test/regress/sql/stats_ext.sql @@ -335,7 +335,7 @@ SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 26, 51, 76) AND b IN (''1'', ''26'') AND c IN (1)'); -SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 2, 26, 27, 51, 52, 76, 77) AND b IN (''1'', ''2'', ''26'', ''27'') AND c IN (1, 2)'); +--SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 2, 26, 27, 51, 52, 76, 77) AND b IN (''1'', ''2'', ''26'', ''27'') AND c IN (1, 2)'); -- OR clauses referencing the same attribute SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a = 1 OR a = 51) AND b = ''1'''); @@ -358,7 +358,7 @@ SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = ANY (ARRAY[1, 26, 51, 76]) AND b = ANY (ARRAY[''1'', ''26'']) AND c = ANY (ARRAY[1])'); -SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = ANY (ARRAY[1, 2, 26, 27, 51, 52, 76, 77]) AND b = ANY (ARRAY[''1'', ''2'', ''26'', ''27'']) AND c = ANY (ARRAY[1, 2])'); +--SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = ANY (ARRAY[1, 2, 26, 27, 51, 52, 76, 77]) AND b = ANY (ARRAY[''1'', ''2'', ''26'', ''27'']) AND c = ANY (ARRAY[1, 2])'); -- ANY with inequalities should not benefit from functional dependencies SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a < ANY (ARRAY[1, 51]) AND b > ''1'''); @@ -399,7 +399,7 @@ SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 26, 51, 76) AND b IN (''1'', ''26'') AND c IN (1)'); -SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 2, 26, 27, 51, 52, 76, 77) AND b IN (''1'', ''2'', ''26'', ''27'') AND c IN (1, 2)'); +--SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 2, 26, 27, 51, 52, 76, 77) AND b IN (''1'', ''2'', ''26'', ''27'') AND c IN (1, 2)'); -- OR clauses referencing the same attribute SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a = 1 OR a = 51) AND b = ''1'''); @@ -422,7 +422,7 @@ SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = ANY (ARRAY[1, 26, 51, 76]) AND b = ANY (ARRAY[''1'', ''26'']) AND c = ANY (ARRAY[1])'); -SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = ANY (ARRAY[1, 2, 26, 27, 51, 52, 76, 77]) AND b = ANY (ARRAY[''1'', ''2'', ''26'', ''27'']) AND c = ANY (ARRAY[1, 2])'); +--SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = ANY (ARRAY[1, 2, 26, 27, 51, 52, 76, 77]) AND b = ANY (ARRAY[''1'', ''2'', ''26'', ''27'']) AND c = ANY (ARRAY[1, 2])'); -- ANY with inequalities should not benefit from functional dependencies SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a < ANY (ARRAY[1, 51]) AND b > ''1'''); diff --git a/src/test/regress/sql/strings.sql b/src/test/regress/sql/strings.sql index 4b139cfdc50..619fbcf51b0 100644 --- a/src/test/regress/sql/strings.sql +++ b/src/test/regress/sql/strings.sql @@ -176,7 +176,9 @@ SELECT 'abcdefg' SIMILAR TO 'bcd%' AS false; SELECT 'abcdefg' SIMILAR TO '_bcd#%' ESCAPE '#' AS false; SELECT 'abcd%' SIMILAR TO '_bcd#%' ESCAPE '#' AS true; -- Postgres uses '\' as the default escape character, which is not per spec +set standard_conforming_strings=on; SELECT 'abcdefg' SIMILAR TO '_bcd\%' AS false; +reset standard_conforming_strings; -- and an empty string to mean "no escape", which is also not per spec SELECT 'abcd\efg' SIMILAR TO '_bcd\%' ESCAPE '' AS true; -- these behaviors are per spec, though: @@ -605,6 +607,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'); @@ -624,6 +627,7 @@ SELECT get_byte('\x1234567890abcdef00'::bytea, 3); SELECT get_byte('\x1234567890abcdef00'::bytea, 99); -- error SELECT set_byte('\x1234567890abcdef00'::bytea, 7, 11); SELECT set_byte('\x1234567890abcdef00'::bytea, 99, 11); -- error +reset standard_conforming_strings; -- -- test behavior of escape_string_warning and standard_conforming_strings options diff --git a/src/test/regress/sql/subselect.sql b/src/test/regress/sql/subselect.sql index cce8ebdb3d9..691b3f0f60a 100644 --- a/src/test/regress/sql/subselect.sql +++ b/src/test/regress/sql/subselect.sql @@ -768,6 +768,7 @@ insert into sq_limit values (7, 3, 3), (8, 4, 4); +set standard_conforming_strings=on; create function explain_sq_limit() returns setof text language plpgsql as $$ declare ln text; @@ -787,6 +788,7 @@ select * from explain_sq_limit(); select * from (select pk,c2 from sq_limit order by c1,pk) as x limit 3; drop function explain_sq_limit(); +reset standard_conforming_strings; drop table sq_limit; diff --git a/src/test/regress/sql/tsearch.sql b/src/test/regress/sql/tsearch.sql index c958e4df0db..7d4626af6a3 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 @@ -798,3 +800,5 @@ select websearch_to_tsquery(''''); select websearch_to_tsquery('''abc''''def'''); select websearch_to_tsquery('\abc'); select websearch_to_tsquery('\'); + +reset standard_conforming_strings; diff --git a/src/test/regress/sql/unicode.sql b/src/test/regress/sql/unicode.sql index 63cd523f85f..d7ca106a79c 100644 --- a/src/test/regress/sql/unicode.sql +++ b/src/test/regress/sql/unicode.sql @@ -3,6 +3,8 @@ SELECT getdatabaseencoding() <> 'UTF8' AS skip_test \gset \quit \endif +set standard_conforming_strings=on; + SELECT U&'\0061\0308bc' <> U&'\00E4bc' COLLATE "C" AS sanity_check; SELECT normalize(''); @@ -32,3 +34,5 @@ FROM ORDER BY num; SELECT is_normalized('abc', 'def'); -- run-time error + +reset standard_conforming_strings; diff --git a/src/test/regress/sql/xml.sql b/src/test/regress/sql/xml.sql index e3f90db4d56..0e60066482c 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 @@ -617,3 +619,5 @@ SELECT * FROM XMLTABLE('*' PASSING '<e>pre<!--c1--><?pi arg?><![CDATA[&ent1]]><n \x SELECT * FROM XMLTABLE('.' PASSING XMLELEMENT(NAME a) columns a varchar(20) PATH '"<foo/>"', b xml PATH '"<foo/>"'); + +reset standard_conforming_strings; diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm index 67b2ea9ee9b..ad7d4e628be 100644 --- a/src/tools/msvc/Mkvcbuild.pm +++ b/src/tools/msvc/Mkvcbuild.pm @@ -36,8 +36,8 @@ my $contrib_defines = { 'refint' => 'REFINT_VERBOSE' }; my @contrib_uselibpq = ('dblink', 'oid2name', 'postgres_fdw', 'vacuumlo'); my @contrib_uselibpgport = ('oid2name', 'pg_standby', 'vacuumlo'); my @contrib_uselibpgcommon = ('oid2name', 'pg_standby', 'vacuumlo'); -my $contrib_extralibs = undef; -my $contrib_extraincludes = { 'dblink' => ['src/backend'] }; +my $contrib_extralibs = {'mchar' => ['$(ICU46_LIB)\icuin.lib', '$(ICU46_LIB)\icuuc.lib'], 'dbcopies_decoding' => ['$(ICU46_LIB)\icuin.lib', '$(ICU46_LIB)\icuuc.lib']}; +my $contrib_extraincludes = { 'dblink' => ['src/backend'], 'mchar' => ['$(ICU46_INCLUDE)'], 'dbcopies_decoding' => ['$(ICU46_INCLUDE)', 'contrib/mchar'] }; my $contrib_extrasource = { 'cube' => [ 'contrib/cube/cubescan.l', 'contrib/cube/cubeparse.y' ], 'seg' => [ 'contrib/seg/segscan.l', 'contrib/seg/segparse.y' ], @@ -67,11 +67,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'], @@ -473,6 +476,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') { @@ -1036,6 +1040,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;
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor