Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
home:mrbadguy:1C
postgresql15
00001-1C-FULL.patch
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File 00001-1C-FULL.patch of Package postgresql15
diff --git a/contrib/Makefile b/contrib/Makefile index bbf220407b0..e52fbcd133d 100644 --- a/contrib/Makefile +++ b/contrib/Makefile @@ -20,18 +20,23 @@ SUBDIRS = \ dict_int \ dict_xsyn \ earthdistance \ + fasttrun \ file_fdw \ fuzzystrmatch \ + fulleq \ hstore \ intagg \ intarray \ isn \ lo \ ltree \ + mchar \ oid2name \ old_snapshot \ + online_analyze \ pageinspect \ passwordcheck \ + plantuner \ pg_buffercache \ pg_freespacemap \ pg_prewarm \ @@ -51,7 +56,8 @@ SUBDIRS = \ tsm_system_rows \ tsm_system_time \ unaccent \ - vacuumlo + vacuumlo \ + dbcopies_decoding ifeq ($(with_ssl),openssl) SUBDIRS += pgcrypto sslinfo diff --git a/contrib/auto_explain/auto_explain.c b/contrib/auto_explain/auto_explain.c index c9a0d947c83..9b6f28bea98 100644 --- a/contrib/auto_explain/auto_explain.c +++ b/contrib/auto_explain/auto_explain.c @@ -19,6 +19,7 @@ #include "common/pg_prng.h" #include "executor/instrument.h" #include "jit/jit.h" +#include "optimizer/planner.h" #include "utils/guc.h" PG_MODULE_MAGIC; @@ -377,6 +378,7 @@ explain_ExecutorEnd(QueryDesc *queryDesc) if (msec >= auto_explain_log_min_duration) { ExplainState *es = NewExplainState(); + instr_time planduration = queryDesc->plannedstmt->planDuration; es->analyze = (queryDesc->instrument_options && auto_explain_log_analyze); es->verbose = auto_explain_log_verbose; @@ -414,8 +416,9 @@ explain_ExecutorEnd(QueryDesc *queryDesc) * often result in duplication. */ ereport(auto_explain_log_level, - (errmsg("duration: %.3f ms plan:\n%s", - msec, es->str->data), + (errmsg("duration: %.3f ms planning: %.3f ms plan:\n%s", + msec, 1000 * INSTR_TIME_GET_DOUBLE(planduration), + es->str->data), errhidestmt(true))); } diff --git a/contrib/dbcopies_decoding/Makefile b/contrib/dbcopies_decoding/Makefile new file mode 100644 index 00000000000..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..132553cfc27 --- /dev/null +++ b/contrib/dbcopies_decoding/dbcopies_decoding.c @@ -0,0 +1,898 @@ +#include "postgres.h" + +#include "catalog/pg_type.h" + +#include "replication/logical.h" +#include "replication/origin.h" + +#include "utils/builtins.h" +#include "utils/lsyscache.h" +#include "utils/memutils.h" +#include "utils/rel.h" +#include "utils/syscache.h" +#include "utils/catcache.h" +#include "utils/timestamp.h" +#include "utils/cash.h" +#include "utils/pg_locale.h" +#include "utils/date.h" +#include "utils/datetime.h" + +#include "mchar.h" + +PG_MODULE_MAGIC; + + +Oid MCHAROID = InvalidOid; +Oid MVARCHAROID = InvalidOid; +const char cQuoteChar = '\''; +const char cContinueChar = '!'; + +extern PGDLLEXPORT void _PG_init(void); +extern PGDLLEXPORT void _PG_output_plugin_init(OutputPluginCallbacks* cb); + +typedef struct +{ + MemoryContext context; + int record_buf_size; + //Заказанный размер записи + int prepare_header_size; + //Размер заголовка, который записывает OutputPluginPrepareWrite в ctx->out + bool include_xids; + //флаг Записывать идентификатор транзакции + bool skip_change; + //флаг пропустить все, ничего не выводить + bool xact_wrote_changes; + //Признак того, что старт транзакции уже выведен. +} DecodingData; + +static void decode_startup(LogicalDecodingContext* ctx, + OutputPluginOptions* opt, bool is_init); +static void decode_shutdown(LogicalDecodingContext* ctx + ); +static void decode_begin_txn(LogicalDecodingContext* ctx, + ReorderBufferTXN* txn); +static void decode_commit_txn(LogicalDecodingContext* ctx, + ReorderBufferTXN* txn, XLogRecPtr commit_lsn); +static void decode_change(LogicalDecodingContext* ctx, + ReorderBufferTXN* txn, Relation rel, ReorderBufferChange* change); +static void decode_truncate(LogicalDecodingContext* ctx, + ReorderBufferTXN* txn, int nrelations, Relation relations[], + ReorderBufferChange* change); +static bool filter_by_origin(LogicalDecodingContext *ctx, + RepOriginId origin_id); + +#ifndef U8_TRUNCATE_IF_INCOMPLETE +// © 2016 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html +/* +******************************************************************************* +* +* Copyright (C) 1999-2015, International Business Machines +* Corporation and others. All Rights Reserved. +* +******************************************************************************* +* file name: utf8.h +* encoding: UTF-8 +* tab size: 8 (not used) +* indentation:4 +* +* created on: 1999sep13 +* created by: Markus W. Scherer +*/ +#define U8_LEAD4_T1_BITS \ +"\x00\x00\x00\x00\x00\x00\x00\x00\x1E\x0F\x0F\x0F\x00\x00\x00\x00" +#define U8_IS_VALID_LEAD4_AND_T1(lead, t1) \ +(U8_LEAD4_T1_BITS[(uint8_t)(t1)>>4]&(1<<((lead)&7))) +#define U8_LEAD3_T1_BITS \ +"\x20\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x10\x30\x30" +#define U8_IS_VALID_LEAD3_AND_T1(lead, t1) \ +(U8_LEAD3_T1_BITS[(lead)&0xf]&(1<<((uint8_t)(t1)>>5))) +#define U8_TRUNCATE_IF_INCOMPLETE(s, start, length) { \ + if((length)>(start)) { \ + uint8_t __b1=s[(length)-1]; \ + if(U8_IS_SINGLE(__b1)) { \ + /* common ASCII character */ \ + } else if(U8_IS_LEAD(__b1)) { \ + --(length); \ + } else if(U8_IS_TRAIL(__b1) && ((length)-2)>=(start)) { \ + uint8_t __b2=s[(length)-2]; \ + if(0xe0<=__b2 && __b2<=0xf4) { \ + if(__b2<0xf0 ? U8_IS_VALID_LEAD3_AND_T1(__b2, __b1) : \ + U8_IS_VALID_LEAD4_AND_T1(__b2, __b1)) { \ + (length)-=2; \ + } \ + } else if(U8_IS_TRAIL(__b2) && ((length)-3)>=(start)) { \ + uint8_t __b3=s[(length)-3]; \ + if(0xf0<=__b3 && __b3<=0xf4 && \ + U8_IS_VALID_LEAD4_AND_T1(__b3, __b2)) { \ + (length)-=3; \ + } \ + } \ + } \ + } \ +} + +#endif + +void _PG_init(void) +{ +} + +void _PG_output_plugin_init(OutputPluginCallbacks* cb) +{ + AssertVariableIsOfType(&_PG_output_plugin_init, LogicalOutputPluginInit); + + cb->startup_cb = decode_startup; + cb->begin_cb = decode_begin_txn; + cb->change_cb = decode_change; + cb->truncate_cb = decode_truncate; + cb->commit_cb = decode_commit_txn; + cb->shutdown_cb = decode_shutdown; + cb->filter_by_origin_cb = filter_by_origin; +} + +static bool tryExtractBoolOption(DefElem* elem, const char* name, bool* dest) +{ + if (strcmp(elem->defname, name) == 0) + { + if (elem->arg != NULL) + { + if (!parse_bool(strVal(elem->arg), dest)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("could not parse value \"%s\" for parameter \"%s\"", + strVal(elem->arg), elem->defname))); + } + return true; + } + else + return false; +} + +static bool tryExtractIntOption(DefElem* elem, const char* name, int32* dest) +{ + if (strcmp(elem->defname, name) == 0) + { + if (elem->arg != NULL) + *dest = pg_strtoint32(strVal(elem->arg)); + return true; + } + else + return false; +} + +static void readTypeOID(char* typeName, Oid* typeOid) +{ + if (*typeOid == InvalidOid) + { + CatCList* catlist = SearchSysCacheList(TYPENAMENSP, + 1, CStringGetDatum(typeName), 0, 0); + if (catlist->n_members == 1) + *typeOid = ((Form_pg_type)GETSTRUCT( + &catlist->members[0]->tuple))->oid; + ReleaseSysCacheList(catlist); + + if (*typeOid == InvalidOid) + elog(WARNING, "OID of type %s not defined!", typeName); + } +} + +static void decode_startup(LogicalDecodingContext* ctx, + OutputPluginOptions* opt, bool is_init) +{ + ListCell* option; + DecodingData* data = palloc0(sizeof(DecodingData)); + + data->include_xids = true; + data->skip_change = false; + data->record_buf_size = ALLOCSET_DEFAULT_MAXSIZE / 4; + foreach(option, ctx->output_plugin_options) + { + DefElem* elem = lfirst(option); + Assert(elem->arg == NULL || IsA(elem->arg, String)); + + if (!tryExtractBoolOption(elem, "include-xids", + &data->include_xids)) + if (!tryExtractBoolOption(elem, "skip-change", + &data->skip_change)) + if (!tryExtractIntOption(elem, "slice_size", + &data->record_buf_size)) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("option \"%s\" = \"%s\" is unknown", + elem->defname, + elem->arg ? strVal(elem->arg) : "(null)") + ) + ); + } + } + data->context = AllocSetContextCreate(ctx->context, + "text conversion context", + ALLOCSET_DEFAULT_SIZES); + ctx->output_plugin_private = data; + + opt->output_type = OUTPUT_PLUGIN_TEXTUAL_OUTPUT; + opt->receive_rewrites = false; +} + +static void decode_shutdown(LogicalDecodingContext* ctx) +{ + DecodingData* data = ctx->output_plugin_private; + + MemoryContextDelete(data->context); +} + +static bool filter_by_origin(LogicalDecodingContext *ctx, + RepOriginId origin_id) +{ + DecodingData* data = ctx->output_plugin_private; + return data && data->skip_change; +} + +static void decode_begin_txn(LogicalDecodingContext* ctx, + ReorderBufferTXN* txn) +{ + DecodingData* data = ctx->output_plugin_private; + + data->xact_wrote_changes = false; +} + +static void decode_commit_txn(LogicalDecodingContext* ctx, + ReorderBufferTXN* txn, XLogRecPtr commit_lsn) +{ + DecodingData* data = ctx->output_plugin_private; + + if (!data->xact_wrote_changes || data->skip_change) + return; + + OutputPluginPrepareWrite(ctx, true); + if (data->include_xids) + appendStringInfo(ctx->out, "C %u", txn->xid); + else + appendStringInfo(ctx->out, "C"); + OutputPluginWrite(ctx, true); +} + +static int record_buf_size(LogicalDecodingContext* ctx) { + return ((DecodingData*)(ctx->output_plugin_private))->record_buf_size; +} + +static void prepareFlushedCtx(LogicalDecodingContext* ctx) +{ + ctx->out->len = 0; + OutputPluginPrepareWrite(ctx, true); + ((DecodingData*)(ctx->output_plugin_private) + )->prepare_header_size = ctx->out->len; +} + +static int checkFlushCtx(LogicalDecodingContext* ctx, int toWriteSize) +{ //возвращает максимальное число байт, + //которое можно записать до превышения лимита длинны + int overflowRemain = record_buf_size(ctx) - ctx->out->len - 1; + if (overflowRemain <= toWriteSize) + { + appendStringInfoChar(ctx->out, cContinueChar); + switch (((DecodingData*)(ctx->output_plugin_private) + )->prepare_header_size) + { + case 0: + break; + case 1 + sizeof(int64) * 3: + memset(&ctx->out->data[1], 0, sizeof(int64) * 2); + break; + default: + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("Unsupported ctx->prepare_write function!"))); + + } + OutputPluginWrite(ctx, false); + prepareFlushedCtx(ctx); + return record_buf_size(ctx) - ctx->out->len - 1; + } + else + return overflowRemain; +} + +static void printByts(LogicalDecodingContext* ctx, Datum val) +{ + const char n[] = { "0123456789abcdef" }; + const int cDig = 16; + char* bytsData = VARDATA(val); + int32 bytsLen = VARSIZE_ANY_EXHDR(val); + int resultSize = 3 + bytsLen * 2 + 1; //остаток, который требуется записать + int overflowRemain = checkFlushCtx(ctx, 3); + if (resultSize > overflowRemain) + enlargeStringInfo(ctx->out, overflowRemain); + else + enlargeStringInfo(ctx->out, resultSize); + + appendStringInfoString(ctx->out, "\'\\x"); + overflowRemain -= 3; + resultSize -= 3; + + { + int32 i; + for (i = 0; i < bytsLen; ++i) + { + int x; + if (overflowRemain < 2) + { + overflowRemain = checkFlushCtx(ctx, 2); + if (resultSize > overflowRemain) + enlargeStringInfo(ctx->out, overflowRemain); + else + enlargeStringInfo(ctx->out, resultSize); + } + x = bytsData[i] & 255; + ctx->out->data[ctx->out->len] = n[x / cDig]; + ctx->out->data[ctx->out->len + 1] = n[x % cDig]; + ctx->out->len += 2; + overflowRemain -= 2; + resultSize -= 2; + } + } + if (overflowRemain < 1) + checkFlushCtx(ctx, 1); + appendStringInfoChar(ctx->out, cQuoteChar); +} + +static bool truncateIfIncmoplete(const int maxCharSize, const char* str, int* len) +{ + if (maxCharSize == 1) + return true; + else + { + int dbEnc = GetDatabaseEncoding(); + if (dbEnc == PG_UTF8) + { + U8_TRUNCATE_IF_INCOMPLETE(str, 0, *len); + return (len > 0); + } + else + { //медленный экзотичный вариант + int truncCount; + for (truncCount = 1; truncCount < maxCharSize; ++truncCount) + { + int charLen; + for (charLen = 1; + charLen <= maxCharSize && *len >= charLen; + ++charLen) + if (pg_verify_mbstr(dbEnc, &str[*len - charLen], + charLen, true)) + return true; + + --(*len); + } + } + } + + return false; +} + +static void printCharVarchar(LogicalDecodingContext* ctx, Datum val) +{ + const int maxCharSize = pg_database_encoding_max_length(); + char* bytsData = VARDATA(val); + int32 bytsLen = VARSIZE_ANY_EXHDR(val); + int overflowRemain = checkFlushCtx(ctx, 1 + maxCharSize); + + appendStringInfoChar(ctx->out, cQuoteChar); + --overflowRemain; + { + char* pBegin = bytsData; + int L = 0; + int i; + for (i = 0; i < bytsLen; ++i) + { + bool overflow; + ++L; + overflow = (L >= overflowRemain); + if (bytsData[i] == cQuoteChar || overflow || i + 1 == bytsLen) + { + if (overflow && + !(bytsData[i] == cQuoteChar || i + 1 == bytsLen)) + { + if (!truncateIfIncmoplete(maxCharSize, pBegin, &L)) + + ereport(ERROR, + (errcode(ERRCODE_INVALID_CHARACTER_VALUE_FOR_CAST), + errmsg("invalid string value") + ) + ); + } + appendBinaryStringInfo(ctx->out, pBegin, L); + pBegin += L; + if (bytsData[i] == cQuoteChar) + { + overflowRemain = checkFlushCtx(ctx, maxCharSize+1); + appendStringInfoChar(ctx->out, bytsData[i]); + --overflowRemain; + } + else if (overflow) //гарантированный сброс буфера + overflowRemain = checkFlushCtx(ctx, record_buf_size(ctx)); + else + overflowRemain = checkFlushCtx(ctx, maxCharSize); + L = 0; + } + + } + } + if (overflowRemain < 1) + checkFlushCtx(ctx, 1); + appendStringInfoChar(ctx->out, cQuoteChar); +} + +static int printM(const UChar* wordsData, + int wordsLen, LogicalDecodingContext* ctx) +{ + const int maxCharSize = pg_database_encoding_max_length(); + const UChar cQuoteUChar = L'\''; + int overflowRemain = checkFlushCtx(ctx, 1 + maxCharSize); + + appendStringInfoChar(ctx->out, cQuoteChar); + --overflowRemain; + { + const UChar* pBegin = wordsData; + int L = 0; + int i; + for (i = 0; i < wordsLen; ++i) + { + bool overflow; + ++L; + overflow = (L*maxCharSize >= overflowRemain); + if (wordsData[i] == cQuoteUChar || + overflow || + i + 1 == wordsLen) + { + if (overflow && + !(wordsData[i] == cQuoteUChar || i + 1 == wordsLen)) + { + if (U16_IS_LEAD(wordsData[i])) + --L; + + if (L == 0 || (i > 0 && U16_IS_LEAD(wordsData[i - 1]))) + ereport(ERROR, + (errcode(ERRCODE_INVALID_CHARACTER_VALUE_FOR_CAST), + errmsg("invalid utf16 string value") + ) + ); + } + enlargeStringInfo(ctx->out, L * maxCharSize); + ctx->out->len += UChar2Char(pBegin, L, + &ctx->out->data[ctx->out->len]); + pBegin += L; + + if (wordsData[i] == cQuoteUChar) + { + overflowRemain = checkFlushCtx(ctx, maxCharSize+1); + appendStringInfoChar(ctx->out, cQuoteChar); + --overflowRemain; + } + else if (overflow) + overflowRemain = checkFlushCtx(ctx, record_buf_size(ctx)); + else + overflowRemain = checkFlushCtx(ctx, maxCharSize); + L = 0; + } + } + } + return overflowRemain; +} + +static void printMVarchar(LogicalDecodingContext* ctx, Datum val) +{ + const UChar* pBegin = (UChar*)(DatumGetPointer(val) + MVARCHARHDRSZ); + if (printM(pBegin, UVARCHARLENGTH(val), ctx) < 1) + checkFlushCtx(ctx, 1); + appendStringInfoChar(ctx->out, cQuoteChar); +} + +static void printMChar(LogicalDecodingContext* ctx, Datum val) +{ + const UChar* pBegin = (UChar*)(DatumGetPointer(val) + MCHARHDRSZ); + int32 trailBlanksCount = + DatumGetMChar(val)->typmod - u_countChar32(pBegin, UCHARLENGTH(val)); + int overflowRemain = printM(pBegin, UCHARLENGTH(val), ctx); + while (trailBlanksCount > 0) + { + + if (trailBlanksCount > overflowRemain) + { + appendStringInfoSpaces(ctx->out, overflowRemain); + trailBlanksCount -= overflowRemain; + overflowRemain = checkFlushCtx(ctx, 1); + } + else + { + appendStringInfoSpaces(ctx->out, trailBlanksCount); + overflowRemain -= trailBlanksCount; + trailBlanksCount = 0; + } + } + if (overflowRemain < 1) + checkFlushCtx(ctx, 1); + appendStringInfoChar(ctx->out, cQuoteChar); +} + +static void appendCtxString(LogicalDecodingContext* ctx, char* str) +{ + int l = strlen(str); + checkFlushCtx(ctx, l); + appendBinaryStringInfo(ctx->out, str, l); +} + +static void printTimestamp(LogicalDecodingContext* ctx, Datum val) +{ + Timestamp ts = DatumGetTimestamp(val); + if (!TIMESTAMP_NOT_FINITE(ts)) + { + struct pg_tm tm; + fsec_t fsec; + if (timestamp2tm(ts, NULL, &tm, &fsec, NULL, NULL) == 0) + { //отсутствие в параметрах указателя на tz + //приводит к конвертации часов (ts with timezone) + //например было 10:23:54.123+02 получим 08:23:54 + char* str; + checkFlushCtx(ctx, 14); + enlargeStringInfo(ctx->out, 14); + str = ctx->out->data + ctx->out->len; + ctx->out->len += 14; + str = pg_ultostr_zeropad(str, + (tm.tm_year > 0) ? tm.tm_year : -(tm.tm_year - 1), 4); + str = pg_ultostr_zeropad(str, tm.tm_mon, 2); + str = pg_ultostr_zeropad(str, tm.tm_mday, 2); + str = pg_ultostr_zeropad(str, tm.tm_hour, 2); + str = pg_ultostr_zeropad(str, tm.tm_min, 2); + str = pg_ultostr_zeropad(str, Abs(tm.tm_sec), 2); + return; + } + } + appendCtxString(ctx, "'invalid timestamp'"); +} + +static void printDate(LogicalDecodingContext* ctx, Datum val) +{ + DateADT d = DatumGetDateADT(val); + if (!DATE_NOT_FINITE(d)) + { + char* str; + int year, mon, day; + j2date(d + POSTGRES_EPOCH_JDATE, &year, &mon, &day); + checkFlushCtx(ctx, 8); + enlargeStringInfo(ctx->out, 8); + str = ctx->out->data + ctx->out->len; + ctx->out->len += 8; + str = pg_ultostr_zeropad(str, (year > 0) ? year : -(year - 1), 4); + str = pg_ultostr_zeropad(str, mon, 2); + str = pg_ultostr_zeropad(str, day, 2); + return; + } + appendCtxString(ctx, "'invalid date'"); +} + +static void printTime(LogicalDecodingContext* ctx, Datum val) +{ + TimeADT t = DatumGetTimeADT(val); + char* str; + struct pg_tm tm; + fsec_t fsec; + time2tm(t, &tm, &fsec); + checkFlushCtx(ctx, 14); + enlargeStringInfo(ctx->out, 14); + str = ctx->out->data + ctx->out->len; + ctx->out->len += 14; + str = pg_ultostr_zeropad(str, 0, 4); + str = pg_ultostr_zeropad(str, 0, 2); + str = pg_ultostr_zeropad(str, 0, 2); + str = pg_ultostr_zeropad(str, tm.tm_hour, 2); + str = pg_ultostr_zeropad(str, tm.tm_min, 2); + str = pg_ultostr_zeropad(str, Abs(tm.tm_sec), 2); +} + +static void printMoney(LogicalDecodingContext* ctx, Datum val) +{ + Cash v = DatumGetCash(val); + char buf[128]; + char* pBuf = &buf[127]; + bool minus = (v < 0); + struct lconv *lconvert = PGLC_localeconv(); + int points = lconvert->frac_digits; + + if (points < 0 || points > 10) + points = 2; + + buf[127] = 0; + if (minus) + v = -v; + + do { + *(--pBuf) = ((uint64)v % 10) + '0'; + --points; + + if (points == 0) + *(--pBuf) = '.'; + + if (v) + v = ((uint64)v) / 10; + } while (v || points >= 0); + if (minus) + *(--pBuf) = '-'; + + appendCtxString(ctx, pBuf); +} + +static void printBool(LogicalDecodingContext* ctx, Datum val) +{ + appendCtxString(ctx, DatumGetBool(val) ? "true" : "false"); +} + +static void printDefault(LogicalDecodingContext* ctx, + Datum val, Oid typid, Oid typoutput) +{ // Вывод с помощью стандартной OUTPUT функции ..._out + char* dataAsChar = OidOutputFunctionCall(typoutput, val); + switch (typid) + { + case INT2OID: + case INT4OID: + case INT8OID: + case OIDOID: + case FLOAT4OID: + case FLOAT8OID: + case NUMERICOID: + appendCtxString(ctx, dataAsChar); + break; + + case BITOID: + case VARBITOID: + checkFlushCtx(ctx, (int)strlen(dataAsChar) + 3); + appendStringInfo(ctx->out, "B'%s'", dataAsChar); + break; + + default: + { + const int maxCharSize = pg_database_encoding_max_length(); + const char* pBegin; + const char* pEnd = dataAsChar; + int overflowRemain = checkFlushCtx(ctx, maxCharSize + 1); + + appendStringInfoChar(ctx->out, cQuoteChar); + --overflowRemain; + //в отличие от printCharVarchar, + //здесь я не знаю длинну, но точно знаю, что на конце ноль + for (pBegin = dataAsChar; *pBegin; pBegin = pEnd) + { + bool overflow; + while (*pEnd && + *pEnd != cQuoteChar && + (int)(pEnd - pBegin) < overflowRemain) + ++pEnd; + overflow = (int)(pEnd - pBegin) >= overflowRemain; + if (pEnd != pBegin) + { + if (overflow && *pEnd && *pEnd != cQuoteChar) + { + int32 L = (int32)(pEnd - pBegin); + if (!truncateIfIncmoplete(maxCharSize, pBegin, &L)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_CHARACTER_VALUE_FOR_CAST), + errmsg("invalid string value") + )); + pEnd = pBegin + L; + } + appendBinaryStringInfo(ctx->out, + pBegin, (int)(pEnd - pBegin)); + } + + if (*pEnd == cQuoteChar) + { + overflowRemain = checkFlushCtx(ctx, maxCharSize + 2); + appendStringInfoChar(ctx->out, *pEnd); + appendStringInfoChar(ctx->out, *pEnd); + ++pEnd; + overflowRemain -= 2; + } + else if (overflow) + overflowRemain = checkFlushCtx(ctx, record_buf_size(ctx)); + else + overflowRemain = checkFlushCtx(ctx, maxCharSize); + } + if (overflowRemain < 1) + checkFlushCtx(ctx, 1); + appendStringInfoChar(ctx->out, cQuoteChar); + } + break; + } + pfree(dataAsChar); +} + +static void printTuple(LogicalDecodingContext* ctx, + TupleDesc tupdesc, ReorderBufferTupleBuf* tuple, + bool skip_nulls, char* tableName) +{ + + if (tuple == NULL) + appendCtxString(ctx, " (no-tuple-data)"); + else + { + int natt; + for (natt = 0; natt < tupdesc->natts; natt++) + { + bool typisvarlena; + Oid typoutput; + Form_pg_attribute attr = TupleDescAttr(tupdesc, natt); + Oid typid = attr->atttypid; + bool isnull; + Datum origval; + + if (attr->attisdropped) + continue; + if (attr->attnum < 0) // Don't print system columns, + continue;//oid will already have been printed if present. + + origval = heap_getattr(&tuple->tuple, natt + 1, tupdesc, &isnull); + if (isnull) + { + if (!skip_nulls) + appendCtxString(ctx, " null"); + continue; + } + + checkFlushCtx(ctx, 1); + appendStringInfoChar(ctx->out, ' '); + + getTypeOutputInfo(typid, &typoutput, &typisvarlena); + + if (typisvarlena) + { + if (VARATT_IS_EXTERNAL_ONDISK(origval)) + appendCtxString(ctx, "unchanged-toast-datum"); + else + { + Datum val = PointerGetDatum(PG_DETOAST_DATUM(origval)); + + if (typid == BPCHAROID || + typid == VARCHAROID || + typid == TEXTOID) + printCharVarchar(ctx, val); + else if (typid == BYTEAOID) + printByts(ctx, val); + else if (typid > FirstNormalObjectId) { + readTypeOID("mchar", &MCHAROID); + readTypeOID("mvarchar", &MVARCHAROID); + + if (typid == MCHAROID) + printMChar(ctx, val); + else if (typid == MVARCHAROID) + printMVarchar(ctx, val); + else + printDefault(ctx, val, typid, typoutput); + } else + printDefault(ctx, val, typid, typoutput); + + if (DatumGetPointer(val) != DatumGetPointer(origval)) + pfree(DatumGetPointer(val)); + } + + } + else + { + switch (typid) + { + case MONEYOID: + printMoney(ctx, origval); + break; + case TIMESTAMPOID: + case TIMESTAMPTZOID: + printTimestamp(ctx, origval); + break; + case DATEOID: + printDate(ctx, origval); + break; + case TIMEOID: + printTime(ctx, origval); + break; + case BOOLOID: + printBool(ctx, origval); + break; + default: + printDefault(ctx, origval, typid, typoutput); + break; + } + } + } + } +} + +static void printTransaction(DecodingData* data, + LogicalDecodingContext* ctx, ReorderBufferTXN* txn) +{ + if (data->xact_wrote_changes) + return; + + OutputPluginPrepareWrite(ctx, false); + if (data->include_xids) + appendStringInfo(ctx->out, "B %u", txn->xid); + else + appendStringInfoString(ctx->out, "B"); + OutputPluginWrite(ctx, false); + data->xact_wrote_changes = true; +} + +static void decode_change(LogicalDecodingContext* ctx, + ReorderBufferTXN* txn, Relation relation, ReorderBufferChange* change) +{ + DecodingData* data = ctx->output_plugin_private; + if (data->skip_change) + return; + { + MemoryContext old = MemoryContextSwitchTo(data->context); + TupleDesc tupdesc = RelationGetDescr(relation); + char* tableName = RelationGetRelationName(relation); + + printTransaction(data, ctx, txn); + prepareFlushedCtx(ctx); + switch (change->action) + { + case REORDER_BUFFER_CHANGE_INSERT: + { + appendStringInfoString(ctx->out, "I "); + appendStringInfoString(ctx->out, tableName); + printTuple(ctx, tupdesc, change->data.tp.newtuple, false, tableName); + } + break; + case REORDER_BUFFER_CHANGE_UPDATE: + { + appendStringInfoString(ctx->out, "U "); + appendStringInfoString(ctx->out, tableName); + printTuple(ctx, tupdesc, change->data.tp.newtuple, false, tableName); + } + break; + case REORDER_BUFFER_CHANGE_DELETE: + { + appendStringInfoString(ctx->out, "D "); + appendStringInfoString(ctx->out, tableName); + printTuple(ctx, tupdesc, change->data.tp.oldtuple, true, tableName); + } + break; + default: + Assert(false); + } + MemoryContextSwitchTo(old); + } + OutputPluginWrite(ctx, true); + MemoryContextReset(data->context); +} + +static void decode_truncate(LogicalDecodingContext* ctx, + ReorderBufferTXN* txn, int nrelations, + Relation relations[], ReorderBufferChange* change) +{ + int i; + DecodingData* data = ctx->output_plugin_private; + if (data->skip_change) + return; + + printTransaction(data, ctx, txn); + { + MemoryContext old = MemoryContextSwitchTo(data->context); + + OutputPluginPrepareWrite(ctx, true); + + appendStringInfoString(ctx->out, "T "); + + for (i = 0; i < nrelations; i++) + { + if (i > 0) + appendStringInfoString(ctx->out, ", "); + + appendStringInfoString(ctx->out, + RelationGetRelationName(relations[i])); + } + + MemoryContextSwitchTo(old); + } + OutputPluginWrite(ctx, true); + MemoryContextReset(data->context); +} diff --git a/contrib/dbcopies_decoding/expected/simple.out b/contrib/dbcopies_decoding/expected/simple.out new file mode 100644 index 00000000000..4053ec1935a --- /dev/null +++ b/contrib/dbcopies_decoding/expected/simple.out @@ -0,0 +1,28 @@ +-- predictability +SET synchronous_commit = on; +CREATE TABLE replication_example(id SERIAL PRIMARY KEY, somedata int, text varchar(120)); +SELECT 'init' FROM pg_create_logical_replication_slot('dbcopies_slot', 'dbcopies_decoding'); + ?column? +---------- + init +(1 row) + +BEGIN; +INSERT INTO replication_example(somedata, text) VALUES (1, 1); +INSERT INTO replication_example(somedata, text) VALUES (1, 2); +COMMIT; +SELECT data FROM pg_logical_slot_get_changes('dbcopies_slot', NULL, NULL, 'include-xids', '0'); + data +------------------------------- + B + I replication_example 1 1 '1' + I replication_example 2 1 '2' + C +(4 rows) + +SELECT pg_drop_replication_slot('dbcopies_slot'); + pg_drop_replication_slot +-------------------------- + +(1 row) + diff --git a/contrib/dbcopies_decoding/logical.conf b/contrib/dbcopies_decoding/logical.conf new file mode 100644 index 00000000000..367f7066514 --- /dev/null +++ b/contrib/dbcopies_decoding/logical.conf @@ -0,0 +1,2 @@ +wal_level = logical +max_replication_slots = 4 diff --git a/contrib/dbcopies_decoding/sql/simple.sql b/contrib/dbcopies_decoding/sql/simple.sql new file mode 100644 index 00000000000..1e9d2f72323 --- /dev/null +++ b/contrib/dbcopies_decoding/sql/simple.sql @@ -0,0 +1,15 @@ +-- predictability +SET synchronous_commit = on; + +CREATE TABLE replication_example(id SERIAL PRIMARY KEY, somedata int, text varchar(120)); + +SELECT 'init' FROM pg_create_logical_replication_slot('dbcopies_slot', 'dbcopies_decoding'); + +BEGIN; +INSERT INTO replication_example(somedata, text) VALUES (1, 1); +INSERT INTO replication_example(somedata, text) VALUES (1, 2); +COMMIT; + +SELECT data FROM pg_logical_slot_get_changes('dbcopies_slot', NULL, NULL, 'include-xids', '0'); + +SELECT pg_drop_replication_slot('dbcopies_slot'); diff --git a/contrib/fasttrun/Makefile b/contrib/fasttrun/Makefile new file mode 100644 index 00000000000..78e92b86cbe --- /dev/null +++ b/contrib/fasttrun/Makefile @@ -0,0 +1,17 @@ +MODULE_big = fasttrun +OBJS = fasttrun.o +DATA = fasttrun--2.0.sql fasttrun--unpackaged--2.0.sql +DOCS = README.fasttrun +REGRESS = fasttrun +EXTENSION=fasttrun + + +ifdef USE_PGXS +PGXS := $(shell pg_config --pgxs) +include $(PGXS) +else +subdir = contrib/fasttrun +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/fasttrun/README.fasttrun b/contrib/fasttrun/README.fasttrun new file mode 100644 index 00000000000..b6d1b41a6d2 --- /dev/null +++ b/contrib/fasttrun/README.fasttrun @@ -0,0 +1,16 @@ +select fasttruncate('TABLE_NAME'); + +Function truncates the temporary table and doesn't grow +pg_class size. + +Warning: function isn't transaction safe! + +For tests: +create or replace function f() returns void as $$ +begin +for i in 1..1000 +loop + PERFORM fasttruncate('tt1'); +end loop; +end; +$$ language plpgsql; diff --git a/contrib/fasttrun/expected/fasttrun.out b/contrib/fasttrun/expected/fasttrun.out new file mode 100644 index 00000000000..ef64fa6400e --- /dev/null +++ b/contrib/fasttrun/expected/fasttrun.out @@ -0,0 +1,115 @@ +CREATE EXTENSION fasttrun; +create table persist ( a int ); +insert into persist values (1); +select fasttruncate('persist'); +ERROR: Relation isn't a temporary table +insert into persist values (2); +select * from persist order by a; + a +--- + 1 + 2 +(2 rows) + +create temp table temp1 (a int); +insert into temp1 values (1); +BEGIN; +create temp table temp2 (a int); +insert into temp2 values (1); +select * from temp1 order by a; + a +--- + 1 +(1 row) + +select * from temp2 order by a; + a +--- + 1 +(1 row) + +insert into temp1 (select * from generate_series(1,10000)); +insert into temp2 (select * from generate_series(1,11000)); +analyze temp2; +select relname, relpages>0, reltuples>0 from pg_class where relname in ('temp1', 'temp2') order by relname; + relname | ?column? | ?column? +---------+----------+---------- + temp1 | f | f + temp2 | t | t +(2 rows) + +select fasttruncate('temp1'); + fasttruncate +-------------- + +(1 row) + +select fasttruncate('temp2'); + fasttruncate +-------------- + +(1 row) + +insert into temp1 values (-2); +insert into temp2 values (-2); +select * from temp1 order by a; + a +---- + -2 +(1 row) + +select * from temp2 order by a; + a +---- + -2 +(1 row) + +COMMIT; +select * from temp1 order by a; + a +---- + -2 +(1 row) + +select * from temp2 order by a; + a +---- + -2 +(1 row) + +select relname, relpages>0, reltuples>0 from pg_class where relname in ('temp1', 'temp2') order by relname; + relname | ?column? | ?column? +---------+----------+---------- + temp1 | f | f + temp2 | f | f +(2 rows) + +select fasttruncate('temp1'); + fasttruncate +-------------- + +(1 row) + +select fasttruncate('temp2'); + fasttruncate +-------------- + +(1 row) + +select * from temp1 order by a; + a +--- +(0 rows) + +select * from temp2 order by a; + a +--- +(0 rows) + +select relname, relpages>0, reltuples>0 from pg_class where relname in ('temp1', 'temp2') order by relname; + relname | ?column? | ?column? +---------+----------+---------- + temp1 | f | f + temp2 | f | f +(2 rows) + diff --git a/contrib/fasttrun/fasttrun--2.0.sql b/contrib/fasttrun/fasttrun--2.0.sql new file mode 100644 index 00000000000..708c2753151 --- /dev/null +++ b/contrib/fasttrun/fasttrun--2.0.sql @@ -0,0 +1,6 @@ +\echo Use "CREATE EXTENSION fasttrun" to load this file. \quit + + +CREATE OR REPLACE FUNCTION fasttruncate(text) +RETURNS void AS 'MODULE_PATHNAME' +LANGUAGE C RETURNS NULL ON NULL INPUT VOLATILE; diff --git a/contrib/fasttrun/fasttrun--unpackaged--2.0.sql b/contrib/fasttrun/fasttrun--unpackaged--2.0.sql new file mode 100644 index 00000000000..3a071f077e1 --- /dev/null +++ b/contrib/fasttrun/fasttrun--unpackaged--2.0.sql @@ -0,0 +1,3 @@ +\echo Use "CREATE EXTENSION fasttrun FROM unpackaged" to load this file. \quit + +ALTER EXTENSION fasttrun ADD function fasttruncate(text); diff --git a/contrib/fasttrun/fasttrun.c b/contrib/fasttrun/fasttrun.c new file mode 100644 index 00000000000..0a92851c0fc --- /dev/null +++ b/contrib/fasttrun/fasttrun.c @@ -0,0 +1,85 @@ +#include "postgres.h" + +#include "access/genam.h" +#include "access/heapam.h" +#include "miscadmin.h" +#include "storage/lmgr.h" +#include "storage/bufmgr.h" +#include "catalog/namespace.h" +#include "utils/lsyscache.h" +#include "utils/builtins.h" +#include <fmgr.h> +#include <funcapi.h> +#include <access/heapam.h> +#include <catalog/pg_type.h> +#include <catalog/heap.h> +#include <commands/vacuum.h> +#include <utils/regproc.h> +#include <utils/varlena.h> + +#ifdef PG_MODULE_MAGIC +PG_MODULE_MAGIC; +#endif + +PG_FUNCTION_INFO_V1(fasttruncate); +Datum fasttruncate(PG_FUNCTION_ARGS); +Datum +fasttruncate(PG_FUNCTION_ARGS) { + text *name=PG_GETARG_TEXT_P(0); + char *relname; + List *relname_list; + RangeVar *relvar; + Oid relOid; + Relation rel; + bool makeanalyze = false; + + relname = palloc( VARSIZE(name) + 1); + memcpy(relname, VARDATA(name), VARSIZE(name)-VARHDRSZ); + relname[ VARSIZE(name)-VARHDRSZ ] = '\0'; + + relname_list = stringToQualifiedNameList(relname); + relvar = makeRangeVarFromNameList(relname_list); + relOid = RangeVarGetRelid(relvar, AccessExclusiveLock, false); + + if ( get_rel_relkind(relOid) != RELKIND_RELATION ) + elog(ERROR,"Relation isn't a ordinary table"); + + rel = table_open(relOid, NoLock); + + if ( !isTempNamespace(get_rel_namespace(relOid)) ) + elog(ERROR,"Relation isn't a temporary table"); + + heap_truncate(list_make1_oid(relOid)); + + if ( rel->rd_rel->relpages > 0 || rel->rd_rel->reltuples > 0 ) + makeanalyze = true; + + /* + * heap_truncate doesn't unlock the table, + * so we should unlock it. + */ + + table_close(rel, AccessExclusiveLock); + + if ( makeanalyze ) { + VacuumParams params; + VacuumRelation *rel; + + params.options = VACOPT_ANALYZE; + params.freeze_min_age = -1; + params.freeze_table_age = -1; + params.multixact_freeze_min_age = -1; + params.multixact_freeze_table_age = -1; + params.is_wraparound = false; + params.log_min_duration = -1; + + rel = makeNode(VacuumRelation); + rel->relation = relvar; + rel->oid = relOid; + rel->va_cols = NULL; + vacuum(list_make1(rel), ¶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/fulleq/Makefile b/contrib/fulleq/Makefile new file mode 100644 index 00000000000..de8fbfcec41 --- /dev/null +++ b/contrib/fulleq/Makefile @@ -0,0 +1,45 @@ +MODULE_big = fulleq +OBJS = fulleq.o +DOCS = README.fulleq +REGRESS = fulleq +DATA_built = fulleq--2.0.sql fulleq--unpackaged--2.0.sql +EXTENSION=fulleq + +ARGTYPE = bool bytea char name int8 int2 int4 text \ + oid xid cid oidvector float4 float8 macaddr \ + inet cidr varchar date time timestamp timestamptz \ + interval timetz + +EXTRA_CLEAN = fulleq--2.0.sql fulleq--unpackaged--2.0.sql + +ifdef USE_PGXS +PGXS := $(shell pg_config --pgxs) +include $(PGXS) +else +subdir = contrib/fulleq +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif + +all: fulleq--2.0.sql fulleq--unpackaged--2.0.sql + +fulleq--2.0.sql: fulleq.sql.in + /bin/echo '\echo Use "CREATE EXTENSION fulleq" to load this file. \quit' > $@ + for type in $(ARGTYPE); \ + do \ + sed -e "s/ARGTYPE/$$type/g" < $< >> $@; \ + done + +fulleq--unpackaged--2.0.sql: fulleq-unpackaged.sql.in + /bin/echo '\echo Use "CREATE EXTENSION fulleq FROM unpackaged" to load this file. \quit' > $@ + /bin/echo 'DROP OPERATOR CLASS IF EXISTS int2vector_fill_ops USING hash;' >> $@ + /bin/echo 'DROP OPERATOR FAMILY IF EXISTS int2vector_fill_ops USING hash;' >> $@ + /bin/echo 'DROP FUNCTION IF EXISTS fullhash_int2vector(int2vector);' >> $@ + /bin/echo 'DROP OPERATOR IF EXISTS == (int2vector, int2vector);' >> $@ + /bin/echo 'DROP FUNCTION IF EXISTS isfulleq_int2vector(int2vector, int2vector);' >> $@ + for type in $(ARGTYPE); \ + do \ + sed -e "s/ARGTYPE/$$type/g" < $< >> $@; \ + done + diff --git a/contrib/fulleq/README.fulleq b/contrib/fulleq/README.fulleq new file mode 100644 index 00000000000..93bf0cad20e --- /dev/null +++ b/contrib/fulleq/README.fulleq @@ -0,0 +1,2 @@ +Introduce operator == which returns true when +operands are equal or both are nulls. diff --git a/contrib/fulleq/expected/fulleq.out b/contrib/fulleq/expected/fulleq.out new file mode 100644 index 00000000000..452f8593432 --- /dev/null +++ b/contrib/fulleq/expected/fulleq.out @@ -0,0 +1,61 @@ +CREATE EXTENSION fulleq; +select 4::int == 4; + ?column? +---------- + t +(1 row) + +select 4::int == 5; + ?column? +---------- + f +(1 row) + +select 4::int == NULL; + ?column? +---------- + f +(1 row) + +select NULL::int == 5; + ?column? +---------- + f +(1 row) + +select NULL::int == NULL; + ?column? +---------- + t +(1 row) + +select '4'::text == '4'; + ?column? +---------- + t +(1 row) + +select '4'::text == '5'; + ?column? +---------- + f +(1 row) + +select '4'::text == NULL; + ?column? +---------- + f +(1 row) + +select NULL::text == '5'; + ?column? +---------- + f +(1 row) + +select NULL::text == NULL; + ?column? +---------- + t +(1 row) + diff --git a/contrib/fulleq/fulleq-unpackaged.sql.in b/contrib/fulleq/fulleq-unpackaged.sql.in new file mode 100644 index 00000000000..8d759d8221f --- /dev/null +++ b/contrib/fulleq/fulleq-unpackaged.sql.in @@ -0,0 +1,10 @@ +-- For ARGTYPE + +ALTER EXTENSION fulleq ADD FUNCTION isfulleq_ARGTYPE(ARGTYPE, ARGTYPE); + +ALTER EXTENSION fulleq ADD FUNCTION fullhash_ARGTYPE(ARGTYPE); + +ALTER EXTENSION fulleq ADD OPERATOR == (ARGTYPE, ARGTYPE); + +ALTER EXTENSION fulleq ADD OPERATOR CLASS ARGTYPE_fill_ops USING hash; + diff --git a/contrib/fulleq/fulleq.c b/contrib/fulleq/fulleq.c new file mode 100644 index 00000000000..e435be4b93a --- /dev/null +++ b/contrib/fulleq/fulleq.c @@ -0,0 +1,112 @@ +#include "postgres.h" +#include "fmgr.h" +#include "access/hash.h" +#include "catalog/pg_collation.h" +#include "utils/builtins.h" +#include "utils/bytea.h" +#include "utils/timestamp.h" +#include "utils/date.h" + +#ifdef PG_MODULE_MAGIC +PG_MODULE_MAGIC; +#endif + +#define NULLHASHVALUE (-2147483647) + +#define FULLEQ_FUNC(type, cmpfunc, hashfunc) \ +PG_FUNCTION_INFO_V1( isfulleq_##type ); \ +Datum isfulleq_##type(PG_FUNCTION_ARGS); \ +Datum \ +isfulleq_##type(PG_FUNCTION_ARGS) { \ + if ( PG_ARGISNULL(0) && PG_ARGISNULL(1) ) \ + PG_RETURN_BOOL(true); \ + else if ( PG_ARGISNULL(0) || PG_ARGISNULL(1) ) \ + PG_RETURN_BOOL(false); \ + \ + PG_RETURN_DATUM( DirectFunctionCall2Coll( cmpfunc, \ + DEFAULT_COLLATION_OID, \ + PG_GETARG_DATUM(0), \ + PG_GETARG_DATUM(1) \ + ) ); \ +} \ + \ +PG_FUNCTION_INFO_V1( fullhash_##type ); \ +Datum fullhash_##type(PG_FUNCTION_ARGS); \ +Datum \ +fullhash_##type(PG_FUNCTION_ARGS) { \ + if ( PG_ARGISNULL(0) ) \ + PG_RETURN_INT32(NULLHASHVALUE); \ + \ + PG_RETURN_DATUM( DirectFunctionCall1( hashfunc, \ + PG_GETARG_DATUM(0) \ + ) ); \ +} + + +static Datum +hashint2vector(PG_FUNCTION_ARGS) +{ + int2vector *key = (int2vector *) PG_GETARG_POINTER(0); + + return hash_any((unsigned char *) key->values, key->dim1 * sizeof(int16)); +} + +/* + * We don't have a complete set of int2vector support routines, + * but we need int2vectoreq for catcache indexing. + */ +static Datum +int2vectoreq(PG_FUNCTION_ARGS) +{ + int2vector *a = (int2vector *) PG_GETARG_POINTER(0); + int2vector *b = (int2vector *) PG_GETARG_POINTER(1); + + if (a->dim1 != b->dim1) + PG_RETURN_BOOL(false); + PG_RETURN_BOOL(memcmp(a->values, b->values, a->dim1 * sizeof(int16)) == 0); +} + + +FULLEQ_FUNC( bool , booleq , hashchar ); +FULLEQ_FUNC( bytea , byteaeq , hashvarlena ); +FULLEQ_FUNC( char , chareq , hashchar ); +FULLEQ_FUNC( name , nameeq , hashname ); +FULLEQ_FUNC( int8 , int8eq , hashint8 ); +FULLEQ_FUNC( int2 , int2eq , hashint2 ); +FULLEQ_FUNC( int4 , int4eq , hashint4 ); +FULLEQ_FUNC( text , texteq , hashtext ); +FULLEQ_FUNC( oid , oideq , hashoid ); +FULLEQ_FUNC( xid , xideq , hashint4 ); +FULLEQ_FUNC( cid , cideq , hashint4 ); +FULLEQ_FUNC( oidvector , oidvectoreq , hashoidvector ); +FULLEQ_FUNC( float4 , float4eq , hashfloat4 ); +FULLEQ_FUNC( float8 , float8eq , hashfloat8 ); +/*FULLEQ_FUNC( abstime , abstimeeq , hashint4 );*/ +/*FULLEQ_FUNC( reltime , reltimeeq , hashint4 );*/ +FULLEQ_FUNC( macaddr , macaddr_eq , hashmacaddr ); +FULLEQ_FUNC( inet , network_eq , hashinet ); +FULLEQ_FUNC( cidr , network_eq , hashinet ); +FULLEQ_FUNC( varchar , texteq , hashtext ); +FULLEQ_FUNC( date , date_eq , hashint4 ); +FULLEQ_FUNC( time , time_eq , hashfloat8 ); +FULLEQ_FUNC( timestamp , timestamp_eq , hashfloat8 ); +FULLEQ_FUNC( timestamptz , timestamp_eq , hashfloat8 ); +FULLEQ_FUNC( interval , interval_eq , interval_hash ); +FULLEQ_FUNC( timetz , timetz_eq , timetz_hash ); + +/* + * v10 drop * support for int2vector equality and hash operator in commit + * 5c80642aa8de8393b08cd3cbf612b325cedd98dc, but for compatibility + * we still add this operators + */ +FULLEQ_FUNC( int2vector , int2vectoreq , hashint2vector ); + +static Datum +dummy_eq(PG_FUNCTION_ARGS) +{ + elog(ERROR, "unimplemented"); + PG_RETURN_DATUM(0); //keep compiler quiet +} + +FULLEQ_FUNC( abstime , dummy_eq , hashint4 ); +FULLEQ_FUNC( reltime , dummy_eq , hashint4 ); diff --git a/contrib/fulleq/fulleq.control b/contrib/fulleq/fulleq.control new file mode 100644 index 00000000000..30a26c65fff --- /dev/null +++ b/contrib/fulleq/fulleq.control @@ -0,0 +1,5 @@ +comment = 'exact equal operation' +default_version = '2.0' +module_pathname = '$libdir/fulleq' +relocatable = true +trusted = true diff --git a/contrib/fulleq/fulleq.sql.in b/contrib/fulleq/fulleq.sql.in new file mode 100644 index 00000000000..c270647c720 --- /dev/null +++ b/contrib/fulleq/fulleq.sql.in @@ -0,0 +1,25 @@ +-- For ARGTYPE + +CREATE OR REPLACE FUNCTION isfulleq_ARGTYPE(ARGTYPE, ARGTYPE) +RETURNS bool AS 'MODULE_PATHNAME' +LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + +CREATE OR REPLACE FUNCTION fullhash_ARGTYPE(ARGTYPE) +RETURNS int4 AS 'MODULE_PATHNAME' +LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + + +CREATE OPERATOR == ( + LEFTARG = ARGTYPE, + RIGHTARG = ARGTYPE, + PROCEDURE = isfulleq_ARGTYPE, + COMMUTATOR = '==', + RESTRICT = eqsel, + JOIN = eqjoinsel, + HASHES +); + +CREATE OPERATOR CLASS ARGTYPE_fill_ops + FOR TYPE ARGTYPE USING hash AS + OPERATOR 1 ==, + FUNCTION 1 fullhash_ARGTYPE(ARGTYPE); diff --git a/contrib/fulleq/sql/fulleq.sql b/contrib/fulleq/sql/fulleq.sql new file mode 100644 index 00000000000..d43abeb34b7 --- /dev/null +++ b/contrib/fulleq/sql/fulleq.sql @@ -0,0 +1,13 @@ +CREATE EXTENSION fulleq; + +select 4::int == 4; +select 4::int == 5; +select 4::int == NULL; +select NULL::int == 5; +select NULL::int == NULL; + +select '4'::text == '4'; +select '4'::text == '5'; +select '4'::text == NULL; +select NULL::text == '5'; +select NULL::text == NULL; diff --git a/contrib/mchar/Changes b/contrib/mchar/Changes new file mode 100644 index 00000000000..b7f6e0c5718 --- /dev/null +++ b/contrib/mchar/Changes @@ -0,0 +1,20 @@ +2.0 make an extension +0.17 add == operation: + a == b => ( a = b or a is null and b is null ) +0.16 fix pg_dump - now mchar in pg_catalog scheme, not public + fix bug in mvarchar_substr() +0.15 add upper()/lower() +0.14 Add ESCAPE for LIKE, SIMILAR TO [ESCAPE], POSIX regexp +0.13 Outer binary format is now different from + inner: it's just a UTF-16 string +0.12 Fix copy binary +0.11 Force UTF-8 convertor if server_encoding='UTF8' +0.10 add (mchar|mvarchar)_(send|recv) functions to + allow binary copying. Note: that functions + don't recode values. +0.9 index support for like, improve recoding functions +0.8 initial suport for like optimizioation with index: + still thres no algo to find the nearest greater string +0.7 hash indexes and enable a hash joins +0.6 implicit casting mchar-mvarchar + cross type comparison operations diff --git a/contrib/mchar/Makefile b/contrib/mchar/Makefile new file mode 100644 index 00000000000..81826afd296 --- /dev/null +++ b/contrib/mchar/Makefile @@ -0,0 +1,31 @@ +MODULE_big = mchar +OBJS = mchar_io.o mchar_proc.o mchar_op.o mchar_recode.o mchar_like.o +EXTENSION=mchar +DATA = mchar--2.2.1.sql mchar--2.0.1--2.1.sql mchar--2.0--2.1.sql \ + mchar--2.1.1--2.2.sql mchar--2.1--2.2.sql \ + mchar--2.2--2.2.1.sql \ + mchar--unpackaged--2.0.sql +DOCS = README.mchar +REGRESS = init mchar mvarchar mm like compat +ENCODING = UTF8 + +PG_CPPFLAGS=-I/usr/local/include + +ifdef USE_PGXS +PGXS := $(shell pg_config --pgxs) +include $(PGXS) +else +subdir = contrib/mchar +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif + +ifeq ($(PORTNAME),win32) +ICUNAME=icuin +else +ICUNAME=icui18n +endif + +SHLIB_LINK += -L/usr/local/lib -licuuc -l$(ICUNAME) -Wl,-rpath,'$$ORIGIN' + diff --git a/contrib/mchar/README.mchar b/contrib/mchar/README.mchar new file mode 100644 index 00000000000..479a7d1f40a --- /dev/null +++ b/contrib/mchar/README.mchar @@ -0,0 +1,20 @@ +MCHAR & VARCHAR + type modifier + length() + substr(str, pos[, length]) + || - concatenation with any (mchar,mvarchar) arguments + < <= = >= > - case-insensitive comparisons (libICU) + &< &<= &= &>= &> - case-sensitive comparisons (libICU) + implicit casting mchar<->mvarchar + B-tree and hash index + LIKE [ESCAPE] + SIMILAR TO [ESCAPE] + ~ (POSIX regexp) + index support for LIKE + + +Authors: + Oleg Bartunov <oleg@sai.msu.ru> + Teodor Sigaev <teodor@sigaev.ru> + + diff --git a/contrib/mchar/expected/compat.out b/contrib/mchar/expected/compat.out new file mode 100644 index 00000000000..480a286e8f6 --- /dev/null +++ b/contrib/mchar/expected/compat.out @@ -0,0 +1,66 @@ +--- table based checks +select '<' || ch || '>', '<' || vch || '>' from chvch; + ?column? | ?column? +----------------+-------------- + <No spaces > | <No spaces> + <One space > | <One space > + <1 space > | <1 space > +(3 rows) + +select * from chvch where vch = 'One space'; + ch | vch +--------------+------------ + One space | One space +(1 row) + +select * from chvch where vch = 'One space '; + ch | vch +--------------+------------ + One space | One space +(1 row) + +select * from ch where chcol = 'abcd' order by chcol; + chcol +---------------------------------- + abcd + AbcD +(2 rows) + +select * from ch t1 join ch t2 on t1.chcol = t2.chcol order by t1.chcol, t2.chcol; + chcol | chcol +----------------------------------+---------------------------------- + abcd | AbcD + abcd | abcd + AbcD | AbcD + AbcD | abcd + abcz | abcz + defg | dEfg + defg | defg + dEfg | dEfg + dEfg | defg + ee | Ee + ee | ee + Ee | Ee + Ee | ee +(13 rows) + +select * from ch where chcol > 'abcd' and chcol<'ee'; + chcol +---------------------------------- + abcz + defg + dEfg +(3 rows) + +select * from ch order by chcol; + chcol +---------------------------------- + abcd + AbcD + abcz + defg + dEfg + ee + Ee +(7 rows) + diff --git a/contrib/mchar/expected/init.out b/contrib/mchar/expected/init.out new file mode 100644 index 00000000000..7bae978ec35 --- /dev/null +++ b/contrib/mchar/expected/init.out @@ -0,0 +1,18 @@ +CREATE EXTENSION mchar; +create table ch ( + chcol mchar(32) +) without oids; +insert into ch values('abcd'); +insert into ch values('AbcD'); +insert into ch values('abcz'); +insert into ch values('defg'); +insert into ch values('dEfg'); +insert into ch values('ee'); +insert into ch values('Ee'); +create table chvch ( + ch mchar(12), + vch mvarchar(12) +) without oids; +insert into chvch values('No spaces', 'No spaces'); +insert into chvch values('One space ', 'One space '); +insert into chvch values('1 space', '1 space '); diff --git a/contrib/mchar/expected/like.out b/contrib/mchar/expected/like.out new file mode 100644 index 00000000000..a3f47f8c710 --- /dev/null +++ b/contrib/mchar/expected/like.out @@ -0,0 +1,841 @@ +-- simplest examples +-- E061-04 like predicate +set standard_conforming_strings=off; +SELECT 'hawkeye'::mchar LIKE 'h%' AS "true"; + true +------ + t +(1 row) + +SELECT 'hawkeye'::mchar NOT LIKE 'h%' AS "false"; + false +------- + f +(1 row) + +SELECT 'hawkeye'::mchar LIKE 'H%' AS "true"; + true +------ + t +(1 row) + +SELECT 'hawkeye'::mchar NOT LIKE 'H%' AS "false"; + false +------- + f +(1 row) + +SELECT 'hawkeye'::mchar LIKE 'indio%' AS "false"; + false +------- + f +(1 row) + +SELECT 'hawkeye'::mchar NOT LIKE 'indio%' AS "true"; + true +------ + t +(1 row) + +SELECT 'hawkeye'::mchar LIKE 'h%eye' AS "true"; + true +------ + t +(1 row) + +SELECT 'hawkeye'::mchar NOT LIKE 'h%eye' AS "false"; + false +------- + f +(1 row) + +SELECT 'indio'::mchar LIKE '_ndio' AS "true"; + true +------ + t +(1 row) + +SELECT 'indio'::mchar NOT LIKE '_ndio' AS "false"; + false +------- + f +(1 row) + +SELECT 'indio'::mchar LIKE 'in__o' AS "true"; + true +------ + t +(1 row) + +SELECT 'indio'::mchar NOT LIKE 'in__o' AS "false"; + false +------- + f +(1 row) + +SELECT 'indio'::mchar LIKE 'in_o' AS "false"; + false +------- + f +(1 row) + +SELECT 'indio'::mchar NOT LIKE 'in_o' AS "true"; + true +------ + t +(1 row) + +SELECT 'hawkeye'::mvarchar LIKE 'h%' AS "true"; + true +------ + t +(1 row) + +SELECT 'hawkeye'::mvarchar NOT LIKE 'h%' AS "false"; + false +------- + f +(1 row) + +SELECT 'hawkeye'::mvarchar LIKE 'H%' AS "true"; + true +------ + t +(1 row) + +SELECT 'hawkeye'::mvarchar NOT LIKE 'H%' AS "false"; + false +------- + f +(1 row) + +SELECT 'hawkeye'::mvarchar LIKE 'indio%' AS "false"; + false +------- + f +(1 row) + +SELECT 'hawkeye'::mvarchar NOT LIKE 'indio%' AS "true"; + true +------ + t +(1 row) + +SELECT 'hawkeye'::mvarchar LIKE 'h%eye' AS "true"; + true +------ + t +(1 row) + +SELECT 'hawkeye'::mvarchar NOT LIKE 'h%eye' AS "false"; + false +------- + f +(1 row) + +SELECT 'indio'::mvarchar LIKE '_ndio' AS "true"; + true +------ + t +(1 row) + +SELECT 'indio'::mvarchar NOT LIKE '_ndio' AS "false"; + false +------- + f +(1 row) + +SELECT 'indio'::mvarchar LIKE 'in__o' AS "true"; + true +------ + t +(1 row) + +SELECT 'indio'::mvarchar NOT LIKE 'in__o' AS "false"; + false +------- + f +(1 row) + +SELECT 'indio'::mvarchar LIKE 'in_o' AS "false"; + false +------- + f +(1 row) + +SELECT 'indio'::mvarchar NOT LIKE 'in_o' AS "true"; + true +------ + t +(1 row) + +-- unused escape character +SELECT 'hawkeye'::mchar LIKE 'h%'::mchar ESCAPE '#' AS "true"; + true +------ + t +(1 row) + +SELECT 'hawkeye'::mchar NOT LIKE 'h%'::mchar ESCAPE '#' AS "false"; + false +------- + f +(1 row) + +SELECT 'indio'::mchar LIKE 'ind_o'::mchar ESCAPE '$' AS "true"; + true +------ + t +(1 row) + +SELECT 'indio'::mchar NOT LIKE 'ind_o'::mchar ESCAPE '$' AS "false"; + false +------- + f +(1 row) + +-- escape character +-- E061-05 like predicate with escape clause +SELECT 'h%'::mchar LIKE 'h#%'::mchar ESCAPE '#' AS "true"; + true +------ + t +(1 row) + +SELECT 'h%'::mchar NOT LIKE 'h#%'::mchar ESCAPE '#' AS "false"; + false +------- + f +(1 row) + +SELECT 'h%wkeye'::mchar LIKE 'h#%'::mchar ESCAPE '#' AS "false"; + false +------- + f +(1 row) + +SELECT 'h%wkeye'::mchar NOT LIKE 'h#%'::mchar ESCAPE '#' AS "true"; + true +------ + t +(1 row) + +SELECT 'h%wkeye'::mchar LIKE 'h#%%'::mchar ESCAPE '#' AS "true"; + true +------ + t +(1 row) + +SELECT 'h%wkeye'::mchar NOT LIKE 'h#%%'::mchar ESCAPE '#' AS "false"; + false +------- + f +(1 row) + +SELECT 'h%awkeye'::mchar LIKE 'h#%a%k%e'::mchar ESCAPE '#' AS "true"; + true +------ + t +(1 row) + +SELECT 'h%awkeye'::mchar NOT LIKE 'h#%a%k%e'::mchar ESCAPE '#' AS "false"; + false +------- + f +(1 row) + +SELECT 'indio'::mchar LIKE '_ndio'::mchar ESCAPE '$' AS "true"; + true +------ + t +(1 row) + +SELECT 'indio'::mchar NOT LIKE '_ndio'::mchar ESCAPE '$' AS "false"; + false +------- + f +(1 row) + +SELECT 'i_dio'::mchar LIKE 'i$_d_o'::mchar ESCAPE '$' AS "true"; + true +------ + t +(1 row) + +SELECT 'i_dio'::mchar NOT LIKE 'i$_d_o'::mchar ESCAPE '$' AS "false"; + false +------- + f +(1 row) + +SELECT 'i_dio'::mchar LIKE 'i$_nd_o'::mchar ESCAPE '$' AS "false"; + false +------- + f +(1 row) + +SELECT 'i_dio'::mchar NOT LIKE 'i$_nd_o'::mchar ESCAPE '$' AS "true"; + true +------ + t +(1 row) + +SELECT 'i_dio'::mchar LIKE 'i$_d%o'::mchar ESCAPE '$' AS "true"; + true +------ + t +(1 row) + +SELECT 'i_dio'::mchar NOT LIKE 'i$_d%o'::mchar ESCAPE '$' AS "false"; + false +------- + f +(1 row) + +-- escape character same as pattern character +SELECT 'maca'::mchar LIKE 'm%aca' ESCAPE '%'::mchar AS "true"; + true +------ + t +(1 row) + +SELECT 'maca'::mchar NOT LIKE 'm%aca' ESCAPE '%'::mchar AS "false"; + false +------- + f +(1 row) + +SELECT 'ma%a'::mchar LIKE 'm%a%%a' ESCAPE '%'::mchar AS "true"; + true +------ + t +(1 row) + +SELECT 'ma%a'::mchar NOT LIKE 'm%a%%a' ESCAPE '%'::mchar AS "false"; + false +------- + f +(1 row) + +SELECT 'bear'::mchar LIKE 'b_ear' ESCAPE '_'::mchar AS "true"; + true +------ + t +(1 row) + +SELECT 'bear'::mchar NOT LIKE 'b_ear'::mchar ESCAPE '_' AS "false"; + false +------- + f +(1 row) + +SELECT 'be_r'::mchar LIKE 'b_e__r' ESCAPE '_'::mchar AS "true"; + true +------ + t +(1 row) + +SELECT 'be_r'::mchar NOT LIKE 'b_e__r' ESCAPE '_'::mchar AS "false"; + false +------- + f +(1 row) + +SELECT 'be_r'::mchar LIKE '__e__r' ESCAPE '_'::mchar AS "false"; + false +------- + f +(1 row) + +SELECT 'be_r'::mchar NOT LIKE '__e__r'::mchar ESCAPE '_' AS "true"; + true +------ + t +(1 row) + +-- unused escape character +SELECT 'hawkeye'::mvarchar LIKE 'h%'::mvarchar ESCAPE '#' AS "true"; + true +------ + t +(1 row) + +SELECT 'hawkeye'::mvarchar NOT LIKE 'h%'::mvarchar ESCAPE '#' AS "false"; + false +------- + f +(1 row) + +SELECT 'indio'::mvarchar LIKE 'ind_o'::mvarchar ESCAPE '$' AS "true"; + true +------ + t +(1 row) + +SELECT 'indio'::mvarchar NOT LIKE 'ind_o'::mvarchar ESCAPE '$' AS "false"; + false +------- + f +(1 row) + +-- escape character +-- E061-05 like predicate with escape clause +SELECT 'h%'::mvarchar LIKE 'h#%'::mvarchar ESCAPE '#' AS "true"; + true +------ + t +(1 row) + +SELECT 'h%'::mvarchar NOT LIKE 'h#%'::mvarchar ESCAPE '#' AS "false"; + false +------- + f +(1 row) + +SELECT 'h%wkeye'::mvarchar LIKE 'h#%'::mvarchar ESCAPE '#' AS "false"; + false +------- + f +(1 row) + +SELECT 'h%wkeye'::mvarchar NOT LIKE 'h#%'::mvarchar ESCAPE '#' AS "true"; + true +------ + t +(1 row) + +SELECT 'h%wkeye'::mvarchar LIKE 'h#%%'::mvarchar ESCAPE '#' AS "true"; + true +------ + t +(1 row) + +SELECT 'h%wkeye'::mvarchar NOT LIKE 'h#%%'::mvarchar ESCAPE '#' AS "false"; + false +------- + f +(1 row) + +SELECT 'h%awkeye'::mvarchar LIKE 'h#%a%k%e'::mvarchar ESCAPE '#' AS "true"; + true +------ + t +(1 row) + +SELECT 'h%awkeye'::mvarchar NOT LIKE 'h#%a%k%e'::mvarchar ESCAPE '#' AS "false"; + false +------- + f +(1 row) + +SELECT 'indio'::mvarchar LIKE '_ndio'::mvarchar ESCAPE '$' AS "true"; + true +------ + t +(1 row) + +SELECT 'indio'::mvarchar NOT LIKE '_ndio'::mvarchar ESCAPE '$' AS "false"; + false +------- + f +(1 row) + +SELECT 'i_dio'::mvarchar LIKE 'i$_d_o'::mvarchar ESCAPE '$' AS "true"; + true +------ + t +(1 row) + +SELECT 'i_dio'::mvarchar NOT LIKE 'i$_d_o'::mvarchar ESCAPE '$' AS "false"; + false +------- + f +(1 row) + +SELECT 'i_dio'::mvarchar LIKE 'i$_nd_o'::mvarchar ESCAPE '$' AS "false"; + false +------- + f +(1 row) + +SELECT 'i_dio'::mvarchar NOT LIKE 'i$_nd_o'::mvarchar ESCAPE '$' AS "true"; + true +------ + t +(1 row) + +SELECT 'i_dio'::mvarchar LIKE 'i$_d%o'::mvarchar ESCAPE '$' AS "true"; + true +------ + t +(1 row) + +SELECT 'i_dio'::mvarchar NOT LIKE 'i$_d%o'::mvarchar ESCAPE '$' AS "false"; + false +------- + f +(1 row) + +-- escape character same as pattern character +SELECT 'maca'::mvarchar LIKE 'm%aca' ESCAPE '%'::mvarchar AS "true"; + true +------ + t +(1 row) + +SELECT 'maca'::mvarchar NOT LIKE 'm%aca' ESCAPE '%'::mvarchar AS "false"; + false +------- + f +(1 row) + +SELECT 'ma%a'::mvarchar LIKE 'm%a%%a' ESCAPE '%'::mvarchar AS "true"; + true +------ + t +(1 row) + +SELECT 'ma%a'::mvarchar NOT LIKE 'm%a%%a' ESCAPE '%'::mvarchar AS "false"; + false +------- + f +(1 row) + +SELECT 'bear'::mvarchar LIKE 'b_ear' ESCAPE '_'::mvarchar AS "true"; + true +------ + t +(1 row) + +SELECT 'bear'::mvarchar NOT LIKE 'b_ear'::mvarchar ESCAPE '_' AS "false"; + false +------- + f +(1 row) + +SELECT 'be_r'::mvarchar LIKE 'b_e__r' ESCAPE '_'::mvarchar AS "true"; + true +------ + t +(1 row) + +SELECT 'be_r'::mvarchar NOT LIKE 'b_e__r' ESCAPE '_'::mvarchar AS "false"; + false +------- + f +(1 row) + +SELECT 'be_r'::mvarchar LIKE '__e__r' ESCAPE '_'::mvarchar AS "false"; + false +------- + f +(1 row) + +SELECT 'be_r'::mvarchar NOT LIKE '__e__r'::mvarchar ESCAPE '_' AS "true"; + true +------ + t +(1 row) + +-- similar to +SELECT 'abc'::mchar SIMILAR TO 'abc'::mchar AS "true"; + true +------ + t +(1 row) + +SELECT 'abc'::mchar SIMILAR TO 'a'::mchar AS "false"; + false +------- + f +(1 row) + +SELECT 'abc'::mchar SIMILAR TO '%(b|d)%'::mchar AS "true"; + true +------ + t +(1 row) + +SELECT 'abc'::mchar SIMILAR TO '(b|c)%'::mchar AS "false"; + false +------- + f +(1 row) + +SELECT 'h%'::mchar SIMILAR TO 'h#%'::mchar AS "false"; + false +------- + f +(1 row) + +SELECT 'h%'::mchar SIMILAR TO 'h#%'::mchar ESCAPE '#' AS "true"; + true +------ + t +(1 row) + +SELECT 'abc'::mvarchar SIMILAR TO 'abc'::mvarchar AS "true"; + true +------ + t +(1 row) + +SELECT 'abc'::mvarchar SIMILAR TO 'a'::mvarchar AS "false"; + false +------- + f +(1 row) + +SELECT 'abc'::mvarchar SIMILAR TO '%(b|d)%'::mvarchar AS "true"; + true +------ + t +(1 row) + +SELECT 'abc'::mvarchar SIMILAR TO '(b|c)%'::mvarchar AS "false"; + false +------- + f +(1 row) + +SELECT 'h%'::mvarchar SIMILAR TO 'h#%'::mvarchar AS "false"; + false +------- + f +(1 row) + +SELECT 'h%'::mvarchar SIMILAR TO 'h#%'::mvarchar ESCAPE '#' AS "true"; + true +------ + t +(1 row) + +-- index support +SELECT * from ch where chcol like 'aB_d' order by chcol using &<; + chcol +---------------------------------- + AbcD + abcd +(2 rows) + +SELECT * from ch where chcol like 'aB%d' order by chcol using &<; + chcol +---------------------------------- + AbcD + abcd +(2 rows) + +SELECT * from ch where chcol like 'aB%' order by chcol using &<; + chcol +---------------------------------- + AbcD + abcd + abcz +(3 rows) + +SELECT * from ch where chcol like '%BC%' order by chcol using &<; + chcol +---------------------------------- + AbcD + abcd + abcz +(3 rows) + +set enable_seqscan = off; +explain (costs off) +SELECT * from ch where chcol like 'aB_d' order by chcol using &<; + QUERY PLAN +------------------------------------------------------------------------------ + Sort + Sort Key: chcol USING &< + -> Index Only Scan using qq on ch + Index Cond: ((chcol >= 'aB'::mvarchar) AND (chcol < 'aC'::mvarchar)) + Filter: (chcol ~~ 'aB_d'::mvarchar) +(5 rows) + +SELECT * from ch where chcol like 'aB_d' order by chcol using &<; + chcol +---------------------------------- + AbcD + abcd +(2 rows) + +SELECT * from ch where chcol like 'aB%d' order by chcol using &<; + chcol +---------------------------------- + AbcD + abcd +(2 rows) + +SELECT * from ch where chcol like 'aB%' order by chcol using &<; + chcol +---------------------------------- + AbcD + abcd + abcz +(3 rows) + +SELECT * from ch where chcol like '%BC%' order by chcol using &<; + chcol +---------------------------------- + AbcD + abcd + abcz +(3 rows) + +set enable_seqscan = on; +create table testt (f1 mchar(10)); +insert into testt values ('Abc-000001'); +insert into testt values ('Abc-000002'); +insert into testt values ('0000000001'); +insert into testt values ('0000000002'); +select f1 from testt where f1::mvarchar like E'Abc\\-%'::mvarchar; + f1 +------------ + Abc-000001 + Abc-000002 +(2 rows) + +select * from testt where f1::mchar like E'Abc\\-%'::mchar; + f1 +------------ + Abc-000001 + Abc-000002 +(2 rows) + +create index testindex on testt(f1); +set enable_seqscan=off; +explain (costs off) +select f1 from testt where f1::mvarchar like E'Abc\\-%'::mvarchar; + QUERY PLAN +--------------------------------------------------- + Index Only Scan using testindex on testt + Filter: ((f1)::mvarchar ~~ 'Abc\\-%'::mvarchar) +(2 rows) + +select f1 from testt where f1::mvarchar like E'Abc\\-%'::mvarchar; + f1 +------------ + Abc-000001 + Abc-000002 +(2 rows) + +select * from testt where f1::mchar like E'Abc\\-%'::mchar; + f1 +------------ + Abc-000001 + Abc-000002 +(2 rows) + +set enable_seqscan = on; +drop table testt; +create table testt (f1 mvarchar(10)); +insert into testt values ('Abc-000001'); +insert into testt values ('Abc-000002'); +insert into testt values ('0000000001'); +insert into testt values ('0000000002'); +select f1 from testt where f1::mvarchar like E'Abc\\-%'::mvarchar; + f1 +------------ + Abc-000001 + Abc-000002 +(2 rows) + +select * from testt where f1::mchar like E'Abc\\-%'::mchar; + f1 +------------ + Abc-000001 + Abc-000002 +(2 rows) + +select * from testt where f1::mchar like E'Abc\\- %'::mchar; + f1 +------------ + Abc-000001 + Abc-000002 +(2 rows) + +select * from testt where f1::mchar like E' %'::mchar; + f1 +------------ + Abc-000001 + Abc-000002 + 0000000001 + 0000000002 +(4 rows) + +create index testindex on testt(f1); +set enable_seqscan=off; +explain (costs off) +select f1 from testt where f1::mvarchar like E'Abc\\-%'::mvarchar; + QUERY PLAN +---------------------------------------------------------------------- + Index Only Scan using testindex on testt + Index Cond: ((f1 >= 'Abc-'::mvarchar) AND (f1 < 'Abc.'::mvarchar)) + Filter: ((f1)::mvarchar ~~ 'Abc\\-%'::mvarchar) +(3 rows) + +select f1 from testt where f1::mvarchar like E'Abc\\-%'::mvarchar; + f1 +------------ + Abc-000001 + Abc-000002 +(2 rows) + +select * from testt where f1::mchar like E'Abc\\-%'::mchar; + f1 +------------ + Abc-000001 + Abc-000002 +(2 rows) + +select * from testt where f1::mchar like E'Abc\\- %'::mchar; + f1 +------------ + Abc-000001 + Abc-000002 +(2 rows) + +select * from testt where f1::mchar like E' %'::mchar; + f1 +------------ + 0000000001 + 0000000002 + Abc-000001 + Abc-000002 +(4 rows) + +set enable_seqscan = on; +drop table testt; +CREATE TABLE test ( code mchar(5) NOT NULL ); +insert into test values('1111 '); +insert into test values('111 '); +insert into test values('11 '); +insert into test values('1 '); +SELECT * FROM test WHERE code LIKE ('% '); + code +------- + 1 +(1 row) + +set escape_string_warning = off; +SELECT CASE WHEN ('_'::text SIMILAR TO '[\\_]'::text ESCAPE '\\'::text) THEN TRUE ELSE FALSE END ; + case +------ + t +(1 row) + +SELECT CASE WHEN ('_'::mchar SIMILAR TO '[\\_]'::mchar ESCAPE '\\'::mchar) THEN TRUE ELSE FALSE END ; + case +------ + t +(1 row) + +SELECT CASE WHEN ('_'::mvarchar SIMILAR TO '[\\_]'::mvarchar ESCAPE '\\'::mvarchar) THEN TRUE ELSE FALSE END ; + case +------ + t +(1 row) + +reset escape_string_warning; +reset standard_conforming_strings; diff --git a/contrib/mchar/expected/mchar.out b/contrib/mchar/expected/mchar.out new file mode 100644 index 00000000000..f6c592fd16f --- /dev/null +++ b/contrib/mchar/expected/mchar.out @@ -0,0 +1,382 @@ +-- I/O tests +select '1'::mchar; + mchar +------- + 1 +(1 row) + +select '2 '::mchar; + mchar +------- + 2 +(1 row) + +select '10 '::mchar; + mchar +------- + 10 +(1 row) + +select '1'::mchar(2); + mchar +------- + 1 +(1 row) + +select '2 '::mchar(2); + mchar +------- + 2 +(1 row) + +select '3 '::mchar(2); + mchar +------- + 3 +(1 row) + +select '10 '::mchar(2); + mchar +------- + 10 +(1 row) + +select ' '::mchar(10); + mchar +------------ + +(1 row) + +select ' '::mchar; + mchar +------- + +(1 row) + +-- operations & functions +select length('1'::mchar); + length +-------- + 1 +(1 row) + +select length('2 '::mchar); + length +-------- + 1 +(1 row) + +select length('10 '::mchar); + length +-------- + 2 +(1 row) + +select length('1'::mchar(2)); + length +-------- + 1 +(1 row) + +select length('2 '::mchar(2)); + length +-------- + 1 +(1 row) + +select length('3 '::mchar(2)); + length +-------- + 1 +(1 row) + +select length('10 '::mchar(2)); + length +-------- + 2 +(1 row) + +select length(' '::mchar(10)); + length +-------- + 0 +(1 row) + +select length(' '::mchar); + length +-------- + 0 +(1 row) + +select 'asd'::mchar(10) || '>'::mchar(10); + ?column? +---------------------- + asd > +(1 row) + +select length('asd'::mchar(10) || '>'::mchar(10)); + length +-------- + 11 +(1 row) + +select 'asd'::mchar(2) || '>'::mchar(10); + ?column? +-------------- + as> +(1 row) + +select length('asd'::mchar(2) || '>'::mchar(10)); + length +-------- + 3 +(1 row) + +-- Comparisons +select 'asdf'::mchar = 'aSdf'::mchar; + ?column? +---------- + t +(1 row) + +select 'asdf'::mchar = 'aSdf '::mchar; + ?column? +---------- + t +(1 row) + +select 'asdf'::mchar = 'aSdf 1'::mchar(4); + ?column? +---------- + t +(1 row) + +select 'asdf'::mchar = 'aSdf 1'::mchar(5); + ?column? +---------- + t +(1 row) + +select 'asdf'::mchar = 'aSdf 1'::mchar(6); + ?column? +---------- + f +(1 row) + +select 'asdf'::mchar(3) = 'aSdf 1'::mchar(5); + ?column? +---------- + f +(1 row) + +select 'asdf'::mchar(3) = 'aSdf 1'::mchar(3); + ?column? +---------- + t +(1 row) + +select 'asdf'::mchar < 'aSdf'::mchar; + ?column? +---------- + f +(1 row) + +select 'asdf'::mchar < 'aSdf '::mchar; + ?column? +---------- + f +(1 row) + +select 'asdf'::mchar < 'aSdf 1'::mchar(4); + ?column? +---------- + f +(1 row) + +select 'asdf'::mchar < 'aSdf 1'::mchar(5); + ?column? +---------- + f +(1 row) + +select 'asdf'::mchar < 'aSdf 1'::mchar(6); + ?column? +---------- + t +(1 row) + +select 'asdf'::mchar <= 'aSdf'::mchar; + ?column? +---------- + t +(1 row) + +select 'asdf'::mchar <= 'aSdf '::mchar; + ?column? +---------- + t +(1 row) + +select 'asdf'::mchar <= 'aSdf 1'::mchar(4); + ?column? +---------- + t +(1 row) + +select 'asdf'::mchar <= 'aSdf 1'::mchar(5); + ?column? +---------- + t +(1 row) + +select 'asdf'::mchar <= 'aSdf 1'::mchar(6); + ?column? +---------- + t +(1 row) + +select 'asdf'::mchar >= 'aSdf'::mchar; + ?column? +---------- + t +(1 row) + +select 'asdf'::mchar >= 'aSdf '::mchar; + ?column? +---------- + t +(1 row) + +select 'asdf'::mchar >= 'aSdf 1'::mchar(4); + ?column? +---------- + t +(1 row) + +select 'asdf'::mchar >= 'aSdf 1'::mchar(5); + ?column? +---------- + t +(1 row) + +select 'asdf'::mchar >= 'aSdf 1'::mchar(6); + ?column? +---------- + f +(1 row) + +select 'asdf'::mchar > 'aSdf'::mchar; + ?column? +---------- + f +(1 row) + +select 'asdf'::mchar > 'aSdf '::mchar; + ?column? +---------- + f +(1 row) + +select 'asdf'::mchar > 'aSdf 1'::mchar(4); + ?column? +---------- + f +(1 row) + +select 'asdf'::mchar > 'aSdf 1'::mchar(5); + ?column? +---------- + f +(1 row) + +select 'asdf'::mchar > 'aSdf 1'::mchar(6); + ?column? +---------- + f +(1 row) + +select max(ch) from chvch; + max +-------------- + One space +(1 row) + +select min(ch) from chvch; + min +-------------- + 1 space +(1 row) + +select substr('1234567890'::mchar, 3) = '34567890' as "34567890"; + 34567890 +---------- + f +(1 row) + +select substr('1234567890'::mchar, 4, 3) = '456' as "456"; + 456 +----- + t +(1 row) + +select lower('asdfASDF'::mchar); + lower +---------- + asdfasdf +(1 row) + +select upper('asdfASDF'::mchar); + upper +---------- + ASDFASDF +(1 row) + +select 'asd'::mchar == 'aSd'::mchar; + ?column? +---------- + t +(1 row) + +select 'asd'::mchar == 'aCd'::mchar; + ?column? +---------- + f +(1 row) + +select 'asd'::mchar == NULL; + ?column? +---------- + f +(1 row) + +select NULL == 'aCd'::mchar; + ?column? +---------- + f +(1 row) + +select NULL::mchar == NULL; + ?column? +---------- + t +(1 row) + +--Note: here we use different space symbols, be carefull to copy it! +select v, count(*) from +(values (1, '4 242'::mchar), (2, '4 242'), (3, 'aSDF'), (4, 'asdf')) as t(i,v) group by v; + v | count +-------+------- + aSDF | 2 + 4 242 | 2 +(2 rows) + +set enable_hashagg=off; +select v, count(*) from +(values (1, '4 242'::mchar), (2, '4 242'), (3, 'aSDF'), (4, 'asdf')) as t(i,v) group by v; + v | count +-------+------- + 4 242 | 2 + aSDF | 2 +(2 rows) + +reset enable_hashagg; diff --git a/contrib/mchar/expected/mm.out b/contrib/mchar/expected/mm.out new file mode 100644 index 00000000000..c5b36c21611 --- /dev/null +++ b/contrib/mchar/expected/mm.out @@ -0,0 +1,855 @@ +select 'asd'::mchar::mvarchar; + mvarchar +---------- + asd +(1 row) + +select 'asd '::mchar::mvarchar; + mvarchar +---------- + asd +(1 row) + +select 'asd'::mchar(2)::mvarchar; + mvarchar +---------- + as +(1 row) + +select 'asd '::mchar(2)::mvarchar; + mvarchar +---------- + as +(1 row) + +select 'asd'::mchar(5)::mvarchar; + mvarchar +---------- + asd +(1 row) + +select 'asd '::mchar(5)::mvarchar; + mvarchar +---------- + asd +(1 row) + +select 'asd'::mchar::mvarchar(2); + mvarchar +---------- + as +(1 row) + +select 'asd '::mchar::mvarchar(2); + mvarchar +---------- + as +(1 row) + +select 'asd'::mchar(2)::mvarchar(2); + mvarchar +---------- + as +(1 row) + +select 'asd '::mchar(2)::mvarchar(2); + mvarchar +---------- + as +(1 row) + +select 'asd'::mchar(5)::mvarchar(2); + mvarchar +---------- + as +(1 row) + +select 'asd '::mchar(5)::mvarchar(2); + mvarchar +---------- + as +(1 row) + +select 'asd'::mchar::mvarchar(5); + mvarchar +---------- + asd +(1 row) + +select 'asd '::mchar::mvarchar(5); + mvarchar +---------- + asd +(1 row) + +select 'asd'::mchar(2)::mvarchar(5); + mvarchar +---------- + as +(1 row) + +select 'asd '::mchar(2)::mvarchar(5); + mvarchar +---------- + as +(1 row) + +select 'asd'::mchar(5)::mvarchar(5); + mvarchar +---------- + asd +(1 row) + +select 'asd '::mchar(5)::mvarchar(5); + mvarchar +---------- + asd +(1 row) + +select 'asd'::mvarchar::mchar; + mchar +------- + asd +(1 row) + +select 'asd '::mvarchar::mchar; + mchar +------- + asd +(1 row) + +select 'asd'::mvarchar(2)::mchar; + mchar +------- + as +(1 row) + +select 'asd '::mvarchar(2)::mchar; + mchar +------- + as +(1 row) + +select 'asd'::mvarchar(5)::mchar; + mchar +------- + asd +(1 row) + +select 'asd '::mvarchar(5)::mchar; + mchar +------- + asd +(1 row) + +select 'asd'::mvarchar::mchar(2); + mchar +------- + as +(1 row) + +select 'asd '::mvarchar::mchar(2); + mchar +------- + as +(1 row) + +select 'asd'::mvarchar(2)::mchar(2); + mchar +------- + as +(1 row) + +select 'asd '::mvarchar(2)::mchar(2); + mchar +------- + as +(1 row) + +select 'asd'::mvarchar(5)::mchar(2); + mchar +------- + as +(1 row) + +select 'asd '::mvarchar(5)::mchar(2); + mchar +------- + as +(1 row) + +select 'asd'::mvarchar::mchar(5); + mchar +------- + asd +(1 row) + +select 'asd '::mvarchar::mchar(5); + mchar +------- + asd +(1 row) + +select 'asd'::mvarchar(2)::mchar(5); + mchar +------- + as +(1 row) + +select 'asd '::mvarchar(2)::mchar(5); + mchar +------- + as +(1 row) + +select 'asd'::mvarchar(5)::mchar(5); + mchar +------- + asd +(1 row) + +select 'asd '::mvarchar(5)::mchar(5); + mchar +------- + asd +(1 row) + +select 'asd'::mchar || '123'; + ?column? +---------- + asd123 +(1 row) + +select 'asd'::mchar || '123'::mchar; + ?column? +---------- + asd123 +(1 row) + +select 'asd'::mchar || '123'::mvarchar; + ?column? +---------- + asd123 +(1 row) + +select 'asd '::mchar || '123'; + ?column? +---------- + asd123 +(1 row) + +select 'asd '::mchar || '123'::mchar; + ?column? +---------- + asd123 +(1 row) + +select 'asd '::mchar || '123'::mvarchar; + ?column? +---------- + asd123 +(1 row) + +select 'asd '::mchar || '123 '; + ?column? +---------- + asd123 +(1 row) + +select 'asd '::mchar || '123 '::mchar; + ?column? +---------- + asd123 +(1 row) + +select 'asd '::mchar || '123 '::mvarchar; + ?column? +---------- + asd123 +(1 row) + +select 'asd'::mvarchar || '123'; + ?column? +---------- + asd123 +(1 row) + +select 'asd'::mvarchar || '123'::mchar; + ?column? +---------- + asd123 +(1 row) + +select 'asd'::mvarchar || '123'::mvarchar; + ?column? +---------- + asd123 +(1 row) + +select 'asd '::mvarchar || '123'; + ?column? +---------- + asd 123 +(1 row) + +select 'asd '::mvarchar || '123'::mchar; + ?column? +---------- + asd 123 +(1 row) + +select 'asd '::mvarchar || '123'::mvarchar; + ?column? +---------- + asd 123 +(1 row) + +select 'asd '::mvarchar || '123 '; + ?column? +---------- + asd 123 +(1 row) + +select 'asd '::mvarchar || '123 '::mchar; + ?column? +---------- + asd 123 +(1 row) + +select 'asd '::mvarchar || '123 '::mvarchar; + ?column? +---------- + asd 123 +(1 row) + +select 'asd'::mchar(2) || '123'; + ?column? +---------- + as123 +(1 row) + +select 'asd'::mchar(2) || '123'::mchar; + ?column? +---------- + as123 +(1 row) + +select 'asd'::mchar(2) || '123'::mvarchar; + ?column? +---------- + as123 +(1 row) + +select 'asd '::mchar(2) || '123'; + ?column? +---------- + as123 +(1 row) + +select 'asd '::mchar(2) || '123'::mchar; + ?column? +---------- + as123 +(1 row) + +select 'asd '::mchar(2) || '123'::mvarchar; + ?column? +---------- + as123 +(1 row) + +select 'asd '::mchar(2) || '123 '; + ?column? +---------- + as123 +(1 row) + +select 'asd '::mchar(2) || '123 '::mchar; + ?column? +---------- + as123 +(1 row) + +select 'asd '::mchar(2) || '123 '::mvarchar; + ?column? +---------- + as123 +(1 row) + +select 'asd'::mvarchar(2) || '123'; + ?column? +---------- + as123 +(1 row) + +select 'asd'::mvarchar(2) || '123'::mchar; + ?column? +---------- + as123 +(1 row) + +select 'asd'::mvarchar(2) || '123'::mvarchar; + ?column? +---------- + as123 +(1 row) + +select 'asd '::mvarchar(2) || '123'; + ?column? +---------- + as123 +(1 row) + +select 'asd '::mvarchar(2) || '123'::mchar; + ?column? +---------- + as123 +(1 row) + +select 'asd '::mvarchar(2) || '123'::mvarchar; + ?column? +---------- + as123 +(1 row) + +select 'asd '::mvarchar(2) || '123 '; + ?column? +---------- + as123 +(1 row) + +select 'asd '::mvarchar(2) || '123 '::mchar; + ?column? +---------- + as123 +(1 row) + +select 'asd '::mvarchar(2) || '123 '::mvarchar; + ?column? +---------- + as123 +(1 row) + +select 'asd'::mchar(4) || '143'; + ?column? +---------- + asd 143 +(1 row) + +select 'asd'::mchar(4) || '123'::mchar; + ?column? +---------- + asd 123 +(1 row) + +select 'asd'::mchar(4) || '123'::mvarchar; + ?column? +---------- + asd 123 +(1 row) + +select 'asd '::mchar(4) || '123'; + ?column? +---------- + asd 123 +(1 row) + +select 'asd '::mchar(4) || '123'::mchar; + ?column? +---------- + asd 123 +(1 row) + +select 'asd '::mchar(4) || '123'::mvarchar; + ?column? +---------- + asd 123 +(1 row) + +select 'asd '::mchar(4) || '123 '; + ?column? +---------- + asd 123 +(1 row) + +select 'asd '::mchar(4) || '123 '::mchar; + ?column? +---------- + asd 123 +(1 row) + +select 'asd '::mchar(4) || '123 '::mvarchar; + ?column? +---------- + asd 123 +(1 row) + +select 'asd'::mvarchar(4) || '123'; + ?column? +---------- + asd123 +(1 row) + +select 'asd'::mvarchar(4) || '123'::mchar; + ?column? +---------- + asd123 +(1 row) + +select 'asd'::mvarchar(4) || '123'::mvarchar; + ?column? +---------- + asd123 +(1 row) + +select 'asd '::mvarchar(4) || '123'; + ?column? +---------- + asd 123 +(1 row) + +select 'asd '::mvarchar(4) || '123'::mchar; + ?column? +---------- + asd 123 +(1 row) + +select 'asd '::mvarchar(4) || '123'::mvarchar; + ?column? +---------- + asd 123 +(1 row) + +select 'asd '::mvarchar(4) || '123 '; + ?column? +---------- + asd 123 +(1 row) + +select 'asd '::mvarchar(4) || '123 '::mchar; + ?column? +---------- + asd 123 +(1 row) + +select 'asd '::mvarchar(4) || '123 '::mvarchar; + ?column? +---------- + asd 123 +(1 row) + +select 'asd '::mvarchar(4) || '123 '::mchar(4); + ?column? +---------- + asd 123 +(1 row) + +select 'asd '::mvarchar(4) || '123 '::mvarchar(4); + ?column? +---------- + asd 123 +(1 row) + +select 'asd '::mvarchar(4) || '123'::mchar(4); + ?column? +---------- + asd 123 +(1 row) + +select 'asd '::mvarchar(4) || '123'::mvarchar(4); + ?column? +---------- + asd 123 +(1 row) + +select 1 where 'f'::mchar='F'::mvarchar; + ?column? +---------- + 1 +(1 row) + +select 1 where 'f'::mchar='F '::mvarchar; + ?column? +---------- + 1 +(1 row) + +select 1 where 'f '::mchar='F'::mvarchar; + ?column? +---------- + 1 +(1 row) + +select 1 where 'f '::mchar='F '::mvarchar; + ?column? +---------- + 1 +(1 row) + +select 1 where 'f'::mchar='F'::mvarchar(2); + ?column? +---------- + 1 +(1 row) + +select 1 where 'f'::mchar='F '::mvarchar(2); + ?column? +---------- + 1 +(1 row) + +select 1 where 'f '::mchar='F'::mvarchar(2); + ?column? +---------- + 1 +(1 row) + +select 1 where 'f '::mchar='F '::mvarchar(2); + ?column? +---------- + 1 +(1 row) + +select 1 where 'f'::mchar(2)='F'::mvarchar; + ?column? +---------- + 1 +(1 row) + +select 1 where 'f'::mchar(2)='F '::mvarchar; + ?column? +---------- + 1 +(1 row) + +select 1 where 'f '::mchar(2)='F'::mvarchar; + ?column? +---------- + 1 +(1 row) + +select 1 where 'f '::mchar(2)='F '::mvarchar; + ?column? +---------- + 1 +(1 row) + +select 1 where 'f'::mchar(2)='F'::mvarchar(2); + ?column? +---------- + 1 +(1 row) + +select 1 where 'f'::mchar(2)='F '::mvarchar(2); + ?column? +---------- + 1 +(1 row) + +select 1 where 'f '::mchar(2)='F'::mvarchar(2); + ?column? +---------- + 1 +(1 row) + +select 1 where 'f '::mchar(2)='F '::mvarchar(2); + ?column? +---------- + 1 +(1 row) + +select 1 where 'foo'::mchar='FOO'::mvarchar; + ?column? +---------- + 1 +(1 row) + +select 1 where 'foo'::mchar='FOO '::mvarchar; + ?column? +---------- + 1 +(1 row) + +select 1 where 'foo '::mchar='FOO'::mvarchar; + ?column? +---------- + 1 +(1 row) + +select 1 where 'foo '::mchar='FOO '::mvarchar; + ?column? +---------- + 1 +(1 row) + +select 1 where 'foo'::mchar='FOO'::mvarchar(2); + ?column? +---------- +(0 rows) + +select 1 where 'foo'::mchar='FOO '::mvarchar(2); + ?column? +---------- +(0 rows) + +select 1 where 'foo '::mchar='FOO'::mvarchar(2); + ?column? +---------- +(0 rows) + +select 1 where 'foo '::mchar='FOO '::mvarchar(2); + ?column? +---------- +(0 rows) + +select 1 where 'foo'::mchar(2)='FOO'::mvarchar; + ?column? +---------- +(0 rows) + +select 1 where 'foo'::mchar(2)='FOO '::mvarchar; + ?column? +---------- +(0 rows) + +select 1 where 'foo '::mchar(2)='FOO'::mvarchar; + ?column? +---------- +(0 rows) + +select 1 where 'foo '::mchar(2)='FOO '::mvarchar; + ?column? +---------- +(0 rows) + +select 1 where 'foo'::mchar(2)='FOO'::mvarchar(2); + ?column? +---------- + 1 +(1 row) + +select 1 where 'foo'::mchar(2)='FOO '::mvarchar(2); + ?column? +---------- + 1 +(1 row) + +select 1 where 'foo '::mchar(2)='FOO'::mvarchar(2); + ?column? +---------- + 1 +(1 row) + +select 1 where 'foo '::mchar(2)='FOO '::mvarchar(2); + ?column? +---------- + 1 +(1 row) + +Select 'f'::mchar(1) Union Select 'o'::mvarchar(1); + mchar +------- + f + o +(2 rows) + +Select 'f'::mvarchar(1) Union Select 'o'::mchar(1); + mvarchar +---------- + f + o +(2 rows) + +select * from chvch where ch=vch; + ch | vch +--------------+------------ + No spaces | No spaces + One space | One space + 1 space | 1 space +(3 rows) + +select ch.* from ch, (select 'dEfg'::mvarchar as q) as p where chcol > p.q; + chcol +---------------------------------- + ee + Ee +(2 rows) + +create index qq on ch (chcol); +set enable_seqscan=off; +select ch.* from ch, (select 'dEfg'::mvarchar as q) as p where chcol > p.q; + chcol +---------------------------------- + ee + Ee +(2 rows) + +set enable_seqscan=on; +--\copy chvch to 'results/chvch.dump' binary +--truncate table chvch; +--\copy chvch from 'results/chvch.dump' binary +--test joins +CREATE TABLE a (mchar2 MCHAR(2) NOT NULL); +CREATE TABLE c (mvarchar255 mvarchar NOT NULL); +SELECT * FROM a, c WHERE mchar2 = mvarchar255; + mchar2 | mvarchar255 +--------+------------- +(0 rows) + +SELECT * FROM a, c WHERE mvarchar255 = mchar2; + mchar2 | mvarchar255 +--------+------------- +(0 rows) + +DROP TABLE a; +DROP TABLE c; +select * from (values + ('е'::mchar),('ё'),('еа'),('еб'),('ее'),('еж'),('ёа'),('ёб'),('ёё'),('ёж'),('ёе'),('её')) + z order by 1; + column1 +--------- + е + ё + еа + ёа + еб + ёб + ее + её + ёе + ёё + еж + ёж +(12 rows) + +select 'ё'::mchar = 'е'; + ?column? +---------- + f +(1 row) + +select 'Ё'::mchar = 'Е'; + ?column? +---------- + f +(1 row) + +select 'й'::mchar = 'и'; + ?column? +---------- + f +(1 row) + +select 'Й'::mchar = 'И'; + ?column? +---------- + f +(1 row) + +select mvarchar_icase_cmp('ёа','еб'), mvarchar_icase_cmp('еб','ё'), + mvarchar_icase_cmp('ё', 'ёа'); + mvarchar_icase_cmp | mvarchar_icase_cmp | mvarchar_icase_cmp +--------------------+--------------------+-------------------- + -1 | 1 | -1 +(1 row) + diff --git a/contrib/mchar/expected/mvarchar.out b/contrib/mchar/expected/mvarchar.out new file mode 100644 index 00000000000..5c866b43e71 --- /dev/null +++ b/contrib/mchar/expected/mvarchar.out @@ -0,0 +1,363 @@ +-- I/O tests +select '1'::mvarchar; + mvarchar +---------- + 1 +(1 row) + +select '2 '::mvarchar; + mvarchar +---------- + 2 +(1 row) + +select '10 '::mvarchar; + mvarchar +-------------- + 10 +(1 row) + +select '1'::mvarchar(2); + mvarchar +---------- + 1 +(1 row) + +select '2 '::mvarchar(2); + mvarchar +---------- + 2 +(1 row) + +select '3 '::mvarchar(2); + mvarchar +---------- + 3 +(1 row) + +select '10 '::mvarchar(2); + mvarchar +---------- + 10 +(1 row) + +select ' '::mvarchar(10); + mvarchar +------------ + +(1 row) + +select ' '::mvarchar; + mvarchar +-------------------- + +(1 row) + +-- operations & functions +select length('1'::mvarchar); + length +-------- + 1 +(1 row) + +select length('2 '::mvarchar); + length +-------- + 1 +(1 row) + +select length('10 '::mvarchar); + length +-------- + 2 +(1 row) + +select length('1'::mvarchar(2)); + length +-------- + 1 +(1 row) + +select length('2 '::mvarchar(2)); + length +-------- + 1 +(1 row) + +select length('3 '::mvarchar(2)); + length +-------- + 1 +(1 row) + +select length('10 '::mvarchar(2)); + length +-------- + 2 +(1 row) + +select length(' '::mvarchar(10)); + length +-------- + 0 +(1 row) + +select length(' '::mvarchar); + length +-------- + 0 +(1 row) + +select 'asd'::mvarchar(10) || '>'::mvarchar(10); + ?column? +---------- + asd> +(1 row) + +select length('asd'::mvarchar(10) || '>'::mvarchar(10)); + length +-------- + 4 +(1 row) + +select 'asd'::mvarchar(2) || '>'::mvarchar(10); + ?column? +---------- + as> +(1 row) + +select length('asd'::mvarchar(2) || '>'::mvarchar(10)); + length +-------- + 3 +(1 row) + +-- Comparisons +select 'asdf'::mvarchar = 'aSdf'::mvarchar; + ?column? +---------- + t +(1 row) + +select 'asdf'::mvarchar = 'aSdf '::mvarchar; + ?column? +---------- + t +(1 row) + +select 'asdf'::mvarchar = 'aSdf 1'::mvarchar(4); + ?column? +---------- + t +(1 row) + +select 'asdf'::mvarchar = 'aSdf 1'::mvarchar(5); + ?column? +---------- + t +(1 row) + +select 'asdf'::mvarchar = 'aSdf 1'::mvarchar(6); + ?column? +---------- + f +(1 row) + +select 'asdf'::mvarchar(3) = 'aSdf 1'::mvarchar(5); + ?column? +---------- + f +(1 row) + +select 'asdf'::mvarchar(3) = 'aSdf 1'::mvarchar(3); + ?column? +---------- + t +(1 row) + +select 'asdf'::mvarchar < 'aSdf'::mvarchar; + ?column? +---------- + f +(1 row) + +select 'asdf'::mvarchar < 'aSdf '::mvarchar; + ?column? +---------- + f +(1 row) + +select 'asdf'::mvarchar < 'aSdf 1'::mvarchar(4); + ?column? +---------- + f +(1 row) + +select 'asdf'::mvarchar < 'aSdf 1'::mvarchar(5); + ?column? +---------- + f +(1 row) + +select 'asdf'::mvarchar < 'aSdf 1'::mvarchar(6); + ?column? +---------- + t +(1 row) + +select 'asdf'::mvarchar <= 'aSdf'::mvarchar; + ?column? +---------- + t +(1 row) + +select 'asdf'::mvarchar <= 'aSdf '::mvarchar; + ?column? +---------- + t +(1 row) + +select 'asdf'::mvarchar <= 'aSdf 1'::mvarchar(4); + ?column? +---------- + t +(1 row) + +select 'asdf'::mvarchar <= 'aSdf 1'::mvarchar(5); + ?column? +---------- + t +(1 row) + +select 'asdf'::mvarchar <= 'aSdf 1'::mvarchar(6); + ?column? +---------- + t +(1 row) + +select 'asdf'::mvarchar >= 'aSdf'::mvarchar; + ?column? +---------- + t +(1 row) + +select 'asdf'::mvarchar >= 'aSdf '::mvarchar; + ?column? +---------- + t +(1 row) + +select 'asdf'::mvarchar >= 'aSdf 1'::mvarchar(4); + ?column? +---------- + t +(1 row) + +select 'asdf'::mvarchar >= 'aSdf 1'::mvarchar(5); + ?column? +---------- + t +(1 row) + +select 'asdf'::mvarchar >= 'aSdf 1'::mvarchar(6); + ?column? +---------- + f +(1 row) + +select 'asdf'::mvarchar > 'aSdf'::mvarchar; + ?column? +---------- + f +(1 row) + +select 'asdf'::mvarchar > 'aSdf '::mvarchar; + ?column? +---------- + f +(1 row) + +select 'asdf'::mvarchar > 'aSdf 1'::mvarchar(4); + ?column? +---------- + f +(1 row) + +select 'asdf'::mvarchar > 'aSdf 1'::mvarchar(5); + ?column? +---------- + f +(1 row) + +select 'asdf'::mvarchar > 'aSdf 1'::mvarchar(6); + ?column? +---------- + f +(1 row) + +select max(vch) from chvch; + max +------------ + One space +(1 row) + +select min(vch) from chvch; + min +---------- + 1 space +(1 row) + +select substr('1234567890'::mvarchar, 3) = '34567890' as "34567890"; + 34567890 +---------- + f +(1 row) + +select substr('1234567890'::mvarchar, 4, 3) = '456' as "456"; + 456 +----- + t +(1 row) + +select lower('asdfASDF'::mvarchar); + lower +---------- + asdfasdf +(1 row) + +select upper('asdfASDF'::mvarchar); + upper +---------- + ASDFASDF +(1 row) + +select 'asd'::mvarchar == 'aSd'::mvarchar; + ?column? +---------- + t +(1 row) + +select 'asd'::mvarchar == 'aCd'::mvarchar; + ?column? +---------- + f +(1 row) + +select 'asd'::mvarchar == NULL; + ?column? +---------- + f +(1 row) + +select NULL == 'aCd'::mvarchar; + ?column? +---------- + f +(1 row) + +select NULL::mvarchar == NULL; + ?column? +---------- + t +(1 row) + diff --git a/contrib/mchar/mchar--2.0--2.1.sql b/contrib/mchar/mchar--2.0--2.1.sql new file mode 100644 index 00000000000..a794772f376 --- /dev/null +++ b/contrib/mchar/mchar--2.0--2.1.sql @@ -0,0 +1,2 @@ +ALTER FUNCTION mchar_like(mchar, mvarchar) SUPPORT textlike_support; +ALTER FUNCTION mvarchar_like(mvarchar, mvarchar) SUPPORT textlike_support; diff --git a/contrib/mchar/mchar--2.0.1--2.1.sql b/contrib/mchar/mchar--2.0.1--2.1.sql new file mode 100644 index 00000000000..a794772f376 --- /dev/null +++ b/contrib/mchar/mchar--2.0.1--2.1.sql @@ -0,0 +1,2 @@ +ALTER FUNCTION mchar_like(mchar, mvarchar) SUPPORT textlike_support; +ALTER FUNCTION mvarchar_like(mvarchar, mvarchar) SUPPORT textlike_support; diff --git a/contrib/mchar/mchar--2.1--2.2.sql b/contrib/mchar/mchar--2.1--2.2.sql new file mode 100644 index 00000000000..98689671499 --- /dev/null +++ b/contrib/mchar/mchar--2.1--2.2.sql @@ -0,0 +1,20 @@ +CREATE FUNCTION similar_to_escape(mchar) +RETURNS mchar +AS 'MODULE_PATHNAME', 'mchar_similar_escape' +LANGUAGE C IMMUTABLE; + +CREATE FUNCTION similar_to_escape(mchar, mchar) +RETURNS mchar +AS 'MODULE_PATHNAME', 'mchar_similar_escape' +LANGUAGE C IMMUTABLE; + +CREATE FUNCTION similar_to_escape(mvarchar) +RETURNS mvarchar +AS 'MODULE_PATHNAME', 'mvarchar_similar_escape' +LANGUAGE C IMMUTABLE; + +CREATE FUNCTION similar_to_escape(mvarchar, mvarchar) +RETURNS mvarchar +AS 'MODULE_PATHNAME', 'mvarchar_similar_escape' +LANGUAGE C IMMUTABLE; + diff --git a/contrib/mchar/mchar--2.1.1--2.2.sql b/contrib/mchar/mchar--2.1.1--2.2.sql new file mode 100644 index 00000000000..98689671499 --- /dev/null +++ b/contrib/mchar/mchar--2.1.1--2.2.sql @@ -0,0 +1,20 @@ +CREATE FUNCTION similar_to_escape(mchar) +RETURNS mchar +AS 'MODULE_PATHNAME', 'mchar_similar_escape' +LANGUAGE C IMMUTABLE; + +CREATE FUNCTION similar_to_escape(mchar, mchar) +RETURNS mchar +AS 'MODULE_PATHNAME', 'mchar_similar_escape' +LANGUAGE C IMMUTABLE; + +CREATE FUNCTION similar_to_escape(mvarchar) +RETURNS mvarchar +AS 'MODULE_PATHNAME', 'mvarchar_similar_escape' +LANGUAGE C IMMUTABLE; + +CREATE FUNCTION similar_to_escape(mvarchar, mvarchar) +RETURNS mvarchar +AS 'MODULE_PATHNAME', 'mvarchar_similar_escape' +LANGUAGE C IMMUTABLE; + diff --git a/contrib/mchar/mchar--2.2--2.2.1.sql b/contrib/mchar/mchar--2.2--2.2.1.sql new file mode 100644 index 00000000000..e663aa24a5d --- /dev/null +++ b/contrib/mchar/mchar--2.2--2.2.1.sql @@ -0,0 +1,10 @@ +CREATE OR REPLACE FUNCTION mvarchar_support(internal) + RETURNS internal + AS 'MODULE_PATHNAME' + LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT + PARALLEL SAFE; + +ALTER FUNCTION mvarchar(mvarchar, integer, boolean) + SUPPORT mvarchar_support; + + diff --git a/contrib/mchar/mchar--2.2.1.sql b/contrib/mchar/mchar--2.2.1.sql new file mode 100644 index 00000000000..2f975b64edd --- /dev/null +++ b/contrib/mchar/mchar--2.2.1.sql @@ -0,0 +1,1352 @@ +\echo Use "CREATE EXTENSION mchar" to load this file. \quit + +-- I/O functions + +CREATE FUNCTION mchartypmod_in(cstring[]) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mchartypmod_out(int4) +RETURNS cstring +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mchar_in(cstring) +RETURNS mchar +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mchar_out(mchar) +RETURNS cstring +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mchar_send(mchar) +RETURNS bytea +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mchar_recv(internal) +RETURNS mchar +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE TYPE mchar ( + INTERNALLENGTH = -1, + INPUT = mchar_in, + OUTPUT = mchar_out, + TYPMOD_IN = mchartypmod_in, + TYPMOD_OUT = mchartypmod_out, + RECEIVE = mchar_recv, + SEND = mchar_send, + STORAGE = extended +); + +CREATE FUNCTION mchar(mchar, integer, boolean) +RETURNS mchar +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE CAST (mchar as mchar) +WITH FUNCTION mchar(mchar, integer, boolean) as IMPLICIT; + +CREATE FUNCTION mvarchar_in(cstring) +RETURNS mvarchar +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mvarchar_out(mvarchar) +RETURNS cstring +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mvarchar_send(mvarchar) +RETURNS bytea +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mvarchar_recv(internal) +RETURNS mvarchar +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE TYPE mvarchar ( + INTERNALLENGTH = -1, + INPUT = mvarchar_in, + OUTPUT = mvarchar_out, + TYPMOD_IN = mchartypmod_in, + TYPMOD_OUT = mchartypmod_out, + RECEIVE = mvarchar_recv, + SEND = mvarchar_send, + STORAGE = extended +); + +CREATE FUNCTION mvarchar_support(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT +PARALLEL SAFE; + +CREATE FUNCTION mvarchar(mvarchar, integer, boolean) +RETURNS mvarchar +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT +SUPPORT mvarchar_support; + +CREATE CAST (mvarchar as mvarchar) +WITH FUNCTION mvarchar(mvarchar, integer, boolean) as IMPLICIT; + +--Operations and functions + +CREATE FUNCTION length(mchar) +RETURNS int4 +AS 'MODULE_PATHNAME', 'mchar_length' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION upper(mchar) +RETURNS mchar +AS 'MODULE_PATHNAME', 'mchar_upper' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION lower(mchar) +RETURNS mchar +AS 'MODULE_PATHNAME', 'mchar_lower' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mchar_hash(mchar) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mchar_concat(mchar, mchar) +RETURNS mchar +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE OPERATOR || ( + LEFTARG = mchar, + RIGHTARG = mchar, + PROCEDURE = mchar_concat +); + +CREATE FUNCTION mchar_like(mchar, mvarchar) +RETURNS bool +SUPPORT textlike_support +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mchar_notlike(mchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE OPERATOR ~~ ( + LEFTARG = mchar, + RIGHTARG = mvarchar, + PROCEDURE = mchar_like, + RESTRICT = likesel, + JOIN = likejoinsel, + NEGATOR = '!~~' +); + +CREATE OPERATOR !~~ ( + LEFTARG = mchar, + RIGHTARG = mvarchar, + PROCEDURE = mchar_notlike, + RESTRICT = nlikesel, + JOIN = nlikejoinsel, + NEGATOR = '~~' +); + +CREATE FUNCTION mchar_regexeq(mchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mchar_regexne(mchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE OPERATOR ~ ( + LEFTARG = mchar, + RIGHTARG = mchar, + PROCEDURE = mchar_regexeq, + RESTRICT = regexeqsel, + JOIN = regexeqjoinsel, + NEGATOR = '!~' +); + +CREATE OPERATOR !~ ( + LEFTARG = mchar, + RIGHTARG = mchar, + PROCEDURE = mchar_regexne, + RESTRICT = regexnesel, + JOIN = regexnejoinsel, + NEGATOR = '~' +); + +CREATE FUNCTION similar_escape(mchar, mchar) +RETURNS mchar +AS 'MODULE_PATHNAME', 'mchar_similar_escape' +LANGUAGE C IMMUTABLE; + +CREATE FUNCTION length(mvarchar) +RETURNS int4 +AS 'MODULE_PATHNAME', 'mvarchar_length' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION upper(mvarchar) +RETURNS mvarchar +AS 'MODULE_PATHNAME', 'mvarchar_upper' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION lower(mvarchar) +RETURNS mvarchar +AS 'MODULE_PATHNAME', 'mvarchar_lower' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mvarchar_hash(mvarchar) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mvarchar_concat(mvarchar, mvarchar) +RETURNS mvarchar +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE OPERATOR || ( + LEFTARG = mvarchar, + RIGHTARG = mvarchar, + PROCEDURE = mvarchar_concat +); + +CREATE FUNCTION mvarchar_like(mvarchar, mvarchar) +RETURNS bool +SUPPORT textlike_support +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION like_escape(mvarchar, mvarchar) +RETURNS mvarchar +AS 'MODULE_PATHNAME', 'mvarchar_like_escape' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mvarchar_notlike(mvarchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE OPERATOR ~~ ( + LEFTARG = mvarchar, + RIGHTARG = mvarchar, + PROCEDURE = mvarchar_like, + RESTRICT = likesel, + JOIN = likejoinsel, + NEGATOR = '!~~' +); + +CREATE OPERATOR !~~ ( + LEFTARG = mvarchar, + RIGHTARG = mvarchar, + PROCEDURE = mvarchar_notlike, + RESTRICT = nlikesel, + JOIN = nlikejoinsel, + NEGATOR = '~~' +); + +CREATE FUNCTION mvarchar_regexeq(mvarchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mvarchar_regexne(mvarchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE OPERATOR ~ ( + LEFTARG = mvarchar, + RIGHTARG = mvarchar, + PROCEDURE = mvarchar_regexeq, + RESTRICT = regexeqsel, + JOIN = regexeqjoinsel, + NEGATOR = '!~' +); + +CREATE OPERATOR !~ ( + LEFTARG = mvarchar, + RIGHTARG = mvarchar, + PROCEDURE = mvarchar_regexne, + RESTRICT = regexnesel, + JOIN = regexnejoinsel, + NEGATOR = '~' +); + +CREATE FUNCTION similar_escape(mvarchar, mvarchar) +RETURNS mvarchar +AS 'MODULE_PATHNAME', 'mvarchar_similar_escape' +LANGUAGE C IMMUTABLE; + +CREATE FUNCTION substr (mchar, int4) +RETURNS mchar +AS 'MODULE_PATHNAME', 'mchar_substring_no_len' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION substr (mchar, int4, int4) +RETURNS mchar +AS 'MODULE_PATHNAME', 'mchar_substring' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION substr (mvarchar, int4) +RETURNS mvarchar +AS 'MODULE_PATHNAME', 'mvarchar_substring_no_len' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION substr (mvarchar, int4, int4) +RETURNS mvarchar +AS 'MODULE_PATHNAME', 'mvarchar_substring' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +-- Comparing +-- MCHAR + +CREATE FUNCTION mchar_icase_cmp(mchar, mchar) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mchar_icase_eq(mchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mchar_icase_ne(mchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mchar_icase_lt(mchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mchar_icase_le(mchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mchar_icase_gt(mchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mchar_icase_ge(mchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + + +CREATE OPERATOR < ( + LEFTARG = mchar, + RIGHTARG = mchar, + PROCEDURE = mchar_icase_lt, + COMMUTATOR = '>', + NEGATOR = '>=', + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel +); + +CREATE OPERATOR > ( + LEFTARG = mchar, + RIGHTARG = mchar, + PROCEDURE = mchar_icase_gt, + COMMUTATOR = '<', + NEGATOR = '<=', + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel +); + +CREATE OPERATOR <= ( + LEFTARG = mchar, + RIGHTARG = mchar, + PROCEDURE = mchar_icase_le, + COMMUTATOR = '>=', + NEGATOR = '>', + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel +); + +CREATE OPERATOR >= ( + LEFTARG = mchar, + RIGHTARG = mchar, + PROCEDURE = mchar_icase_ge, + COMMUTATOR = '<=', + NEGATOR = '<', + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel +); + +CREATE OPERATOR = ( + LEFTARG = mchar, + RIGHTARG = mchar, + PROCEDURE = mchar_icase_eq, + COMMUTATOR = '=', + NEGATOR = '<>', + RESTRICT = eqsel, + JOIN = eqjoinsel, + SORT1 = '<', + SORT2 = '<', + HASHES +); + +CREATE OPERATOR <> ( + LEFTARG = mchar, + RIGHTARG = mchar, + PROCEDURE = mchar_icase_ne, + COMMUTATOR = '<>', + NEGATOR = '=', + RESTRICT = neqsel, + JOIN = neqjoinsel +); + +CREATE FUNCTION mchar_case_cmp(mchar, mchar) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mchar_case_eq(mchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mchar_case_ne(mchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mchar_case_lt(mchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mchar_case_le(mchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mchar_case_gt(mchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mchar_case_ge(mchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + + +CREATE OPERATOR &< ( + LEFTARG = mchar, + RIGHTARG = mchar, + PROCEDURE = mchar_case_lt, + COMMUTATOR = '&>', + NEGATOR = '&>=', + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel +); + +CREATE OPERATOR &> ( + LEFTARG = mchar, + RIGHTARG = mchar, + PROCEDURE = mchar_case_gt, + COMMUTATOR = '&<', + NEGATOR = '&<=', + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel +); + +CREATE OPERATOR &<= ( + LEFTARG = mchar, + RIGHTARG = mchar, + PROCEDURE = mchar_case_le, + COMMUTATOR = '&>=', + NEGATOR = '&>', + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel +); + +CREATE OPERATOR &>= ( + LEFTARG = mchar, + RIGHTARG = mchar, + PROCEDURE = mchar_case_ge, + COMMUTATOR = '&<=', + NEGATOR = '&<', + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel +); + +CREATE OPERATOR &= ( + LEFTARG = mchar, + RIGHTARG = mchar, + PROCEDURE = mchar_case_eq, + COMMUTATOR = '&=', + NEGATOR = '&<>', + RESTRICT = eqsel, + JOIN = eqjoinsel, + SORT1 = '&<', + SORT2 = '&<' +); + +CREATE OPERATOR &<> ( + LEFTARG = mchar, + RIGHTARG = mchar, + PROCEDURE = mchar_case_ne, + COMMUTATOR = '&<>', + NEGATOR = '&=', + RESTRICT = neqsel, + JOIN = neqjoinsel +); + +--MVARCHAR + +CREATE FUNCTION mvarchar_icase_cmp(mvarchar, mvarchar) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mvarchar_icase_eq(mvarchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mvarchar_icase_ne(mvarchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mvarchar_icase_lt(mvarchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mvarchar_icase_le(mvarchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mvarchar_icase_gt(mvarchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mvarchar_icase_ge(mvarchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + + +CREATE OPERATOR < ( + LEFTARG = mvarchar, + RIGHTARG = mvarchar, + PROCEDURE = mvarchar_icase_lt, + COMMUTATOR = '>', + NEGATOR = '>=', + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel +); + +CREATE OPERATOR > ( + LEFTARG = mvarchar, + RIGHTARG = mvarchar, + PROCEDURE = mvarchar_icase_gt, + COMMUTATOR = '<', + NEGATOR = '<=', + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel +); + +CREATE OPERATOR <= ( + LEFTARG = mvarchar, + RIGHTARG = mvarchar, + PROCEDURE = mvarchar_icase_le, + COMMUTATOR = '>=', + NEGATOR = '>', + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel +); + +CREATE OPERATOR >= ( + LEFTARG = mvarchar, + RIGHTARG = mvarchar, + PROCEDURE = mvarchar_icase_ge, + COMMUTATOR = '<=', + NEGATOR = '<', + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel +); + +CREATE OPERATOR = ( + LEFTARG = mvarchar, + RIGHTARG = mvarchar, + PROCEDURE = mvarchar_icase_eq, + COMMUTATOR = '=', + NEGATOR = '<>', + RESTRICT = eqsel, + JOIN = eqjoinsel, + SORT1 = '<', + SORT2 = '<', + HASHES +); + +CREATE OPERATOR <> ( + LEFTARG = mvarchar, + RIGHTARG = mvarchar, + PROCEDURE = mvarchar_icase_ne, + COMMUTATOR = '<>', + NEGATOR = '=', + RESTRICT = neqsel, + JOIN = neqjoinsel +); + +CREATE FUNCTION mvarchar_case_cmp(mvarchar, mvarchar) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mvarchar_case_eq(mvarchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mvarchar_case_ne(mvarchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mvarchar_case_lt(mvarchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mvarchar_case_le(mvarchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mvarchar_case_gt(mvarchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mvarchar_case_ge(mvarchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + + +CREATE OPERATOR &< ( + LEFTARG = mvarchar, + RIGHTARG = mvarchar, + PROCEDURE = mvarchar_case_lt, + COMMUTATOR = '&>', + NEGATOR = '&>=', + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel +); + +CREATE OPERATOR &> ( + LEFTARG = mvarchar, + RIGHTARG = mvarchar, + PROCEDURE = mvarchar_case_gt, + COMMUTATOR = '&<', + NEGATOR = '&<=', + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel +); + +CREATE OPERATOR &<= ( + LEFTARG = mvarchar, + RIGHTARG = mvarchar, + PROCEDURE = mvarchar_case_le, + COMMUTATOR = '&>=', + NEGATOR = '&>', + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel +); + +CREATE OPERATOR &>= ( + LEFTARG = mvarchar, + RIGHTARG = mvarchar, + PROCEDURE = mvarchar_case_ge, + COMMUTATOR = '&<=', + NEGATOR = '&<', + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel +); + +CREATE OPERATOR &= ( + LEFTARG = mvarchar, + RIGHTARG = mvarchar, + PROCEDURE = mvarchar_case_eq, + COMMUTATOR = '&=', + NEGATOR = '&<>', + RESTRICT = eqsel, + JOIN = eqjoinsel, + SORT1 = '&<', + SORT2 = '&<' +); + +CREATE OPERATOR &<> ( + LEFTARG = mvarchar, + RIGHTARG = mvarchar, + PROCEDURE = mvarchar_case_ne, + COMMUTATOR = '&<>', + NEGATOR = '&=', + RESTRICT = neqsel, + JOIN = neqjoinsel +); + +-- MCHAR <> MVARCHAR + +CREATE FUNCTION mc_mv_icase_cmp(mchar, mvarchar) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mc_mv_icase_eq(mchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mc_mv_icase_ne(mchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mc_mv_icase_lt(mchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mc_mv_icase_le(mchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mc_mv_icase_gt(mchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mc_mv_icase_ge(mchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + + +CREATE OPERATOR < ( + LEFTARG = mchar, + RIGHTARG = mvarchar, + PROCEDURE = mc_mv_icase_lt, + COMMUTATOR = '>', + NEGATOR = '>=', + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel +); + +CREATE OPERATOR > ( + LEFTARG = mchar, + RIGHTARG = mvarchar, + PROCEDURE = mc_mv_icase_gt, + COMMUTATOR = '<', + NEGATOR = '<=', + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel +); + +CREATE OPERATOR <= ( + LEFTARG = mchar, + RIGHTARG = mvarchar, + PROCEDURE = mc_mv_icase_le, + COMMUTATOR = '>=', + NEGATOR = '>', + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel +); + +CREATE OPERATOR >= ( + LEFTARG = mchar, + RIGHTARG = mvarchar, + PROCEDURE = mc_mv_icase_ge, + COMMUTATOR = '<=', + NEGATOR = '<', + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel +); + +CREATE OPERATOR = ( + LEFTARG = mchar, + RIGHTARG = mvarchar, + PROCEDURE = mc_mv_icase_eq, + COMMUTATOR = '=', + NEGATOR = '<>', + RESTRICT = eqsel, + JOIN = eqjoinsel, + SORT1 = '<', + SORT2 = '<' +); + +CREATE OPERATOR <> ( + LEFTARG = mchar, + RIGHTARG = mvarchar, + PROCEDURE = mc_mv_icase_ne, + COMMUTATOR = '<>', + NEGATOR = '=', + RESTRICT = neqsel, + JOIN = neqjoinsel +); + +CREATE FUNCTION mc_mv_case_cmp(mchar, mvarchar) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mc_mv_case_eq(mchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mc_mv_case_ne(mchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mc_mv_case_lt(mchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mc_mv_case_le(mchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mc_mv_case_gt(mchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mc_mv_case_ge(mchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + + +CREATE OPERATOR &< ( + LEFTARG = mchar, + RIGHTARG = mvarchar, + PROCEDURE = mc_mv_case_lt, + COMMUTATOR = '&>', + NEGATOR = '&>=', + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel +); + +CREATE OPERATOR &> ( + LEFTARG = mchar, + RIGHTARG = mvarchar, + PROCEDURE = mc_mv_case_gt, + COMMUTATOR = '&<', + NEGATOR = '&<=', + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel +); + +CREATE OPERATOR &<= ( + LEFTARG = mchar, + RIGHTARG = mvarchar, + PROCEDURE = mc_mv_case_le, + COMMUTATOR = '&>=', + NEGATOR = '&>', + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel +); + +CREATE OPERATOR &>= ( + LEFTARG = mchar, + RIGHTARG = mvarchar, + PROCEDURE = mc_mv_case_ge, + COMMUTATOR = '&<=', + NEGATOR = '&<', + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel +); + +CREATE OPERATOR &= ( + LEFTARG = mchar, + RIGHTARG = mvarchar, + PROCEDURE = mc_mv_case_eq, + COMMUTATOR = '&=', + NEGATOR = '&<>', + RESTRICT = eqsel, + JOIN = eqjoinsel, + SORT1 = '&<', + SORT2 = '&<' +); + +CREATE OPERATOR &<> ( + LEFTARG = mchar, + RIGHTARG = mvarchar, + PROCEDURE = mc_mv_case_ne, + COMMUTATOR = '&<>', + NEGATOR = '&=', + RESTRICT = neqsel, + JOIN = neqjoinsel +); + +-- MVARCHAR <> MCHAR + +CREATE FUNCTION mv_mc_icase_cmp(mvarchar, mchar) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mv_mc_icase_eq(mvarchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mv_mc_icase_ne(mvarchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mv_mc_icase_lt(mvarchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mv_mc_icase_le(mvarchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mv_mc_icase_gt(mvarchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mv_mc_icase_ge(mvarchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + + +CREATE OPERATOR < ( + LEFTARG = mvarchar, + RIGHTARG = mchar, + PROCEDURE = mv_mc_icase_lt, + COMMUTATOR = '>', + NEGATOR = '>=', + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel +); + +CREATE OPERATOR > ( + LEFTARG = mvarchar, + RIGHTARG = mchar, + PROCEDURE = mv_mc_icase_gt, + COMMUTATOR = '<', + NEGATOR = '<=', + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel +); + +CREATE OPERATOR <= ( + LEFTARG = mvarchar, + RIGHTARG = mchar, + PROCEDURE = mv_mc_icase_le, + COMMUTATOR = '>=', + NEGATOR = '>', + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel +); + +CREATE OPERATOR >= ( + LEFTARG = mvarchar, + RIGHTARG = mchar, + PROCEDURE = mv_mc_icase_ge, + COMMUTATOR = '<=', + NEGATOR = '<', + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel +); + +CREATE OPERATOR = ( + LEFTARG = mvarchar, + RIGHTARG = mchar, + PROCEDURE = mv_mc_icase_eq, + COMMUTATOR = '=', + NEGATOR = '<>', + RESTRICT = eqsel, + JOIN = eqjoinsel, + SORT1 = '<', + SORT2 = '<' +); + +CREATE OPERATOR <> ( + LEFTARG = mvarchar, + RIGHTARG = mchar, + PROCEDURE = mv_mc_icase_ne, + COMMUTATOR = '<>', + NEGATOR = '=', + RESTRICT = neqsel, + JOIN = neqjoinsel +); + +CREATE FUNCTION mv_mc_case_cmp(mvarchar, mchar) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mv_mc_case_eq(mvarchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mv_mc_case_ne(mvarchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mv_mc_case_lt(mvarchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mv_mc_case_le(mvarchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mv_mc_case_gt(mvarchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mv_mc_case_ge(mvarchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + + +CREATE OPERATOR &< ( + LEFTARG = mvarchar, + RIGHTARG = mchar, + PROCEDURE = mv_mc_case_lt, + COMMUTATOR = '&>', + NEGATOR = '&>=', + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel +); + +CREATE OPERATOR &> ( + LEFTARG = mvarchar, + RIGHTARG = mchar, + PROCEDURE = mv_mc_case_gt, + COMMUTATOR = '&<', + NEGATOR = '&<=', + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel +); + +CREATE OPERATOR &<= ( + LEFTARG = mvarchar, + RIGHTARG = mchar, + PROCEDURE = mv_mc_case_le, + COMMUTATOR = '&>=', + NEGATOR = '&>', + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel +); + +CREATE OPERATOR &>= ( + LEFTARG = mvarchar, + RIGHTARG = mchar, + PROCEDURE = mv_mc_case_ge, + COMMUTATOR = '&<=', + NEGATOR = '&<', + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel +); + +CREATE OPERATOR &= ( + LEFTARG = mvarchar, + RIGHTARG = mchar, + PROCEDURE = mv_mc_case_eq, + COMMUTATOR = '&=', + NEGATOR = '&<>', + RESTRICT = eqsel, + JOIN = eqjoinsel, + SORT1 = '&<', + SORT2 = '&<' +); + +CREATE OPERATOR &<> ( + LEFTARG = mvarchar, + RIGHTARG = mchar, + PROCEDURE = mv_mc_case_ne, + COMMUTATOR = '&<>', + NEGATOR = '&=', + RESTRICT = neqsel, + JOIN = neqjoinsel +); + +-- MCHAR - VARCHAR operations + +CREATE FUNCTION mchar_mvarchar_concat(mchar, mvarchar) +RETURNS mvarchar +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE OPERATOR || ( + LEFTARG = mchar, + RIGHTARG = mvarchar, + PROCEDURE = mchar_mvarchar_concat +); + +CREATE FUNCTION mvarchar_mchar_concat(mvarchar, mchar) +RETURNS mvarchar +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE OPERATOR || ( + LEFTARG = mvarchar, + RIGHTARG = mchar, + PROCEDURE = mvarchar_mchar_concat +); + +CREATE FUNCTION mvarchar_mchar(mvarchar, integer, boolean) +RETURNS mchar +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE CAST (mvarchar as mchar) +WITH FUNCTION mvarchar_mchar(mvarchar, integer, boolean) as IMPLICIT; + +CREATE FUNCTION mchar_mvarchar(mchar, integer, boolean) +RETURNS mvarchar +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE CAST (mchar as mvarchar) +WITH FUNCTION mchar_mvarchar(mchar, integer, boolean) as IMPLICIT; + +-- Aggregates + +CREATE FUNCTION mchar_larger(mchar, mchar) +RETURNS mchar +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE AGGREGATE max ( + BASETYPE = mchar, + SFUNC = mchar_larger, + STYPE = mchar, + SORTOP = '>' +); + +CREATE FUNCTION mchar_smaller(mchar, mchar) +RETURNS mchar +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE AGGREGATE min ( + BASETYPE = mchar, + SFUNC = mchar_smaller, + STYPE = mchar, + SORTOP = '<' +); + +CREATE FUNCTION mvarchar_larger(mvarchar, mvarchar) +RETURNS mvarchar +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE AGGREGATE max ( + BASETYPE = mvarchar, + SFUNC = mvarchar_larger, + STYPE = mvarchar, + SORTOP = '>' +); + +CREATE FUNCTION mvarchar_smaller(mvarchar, mvarchar) +RETURNS mvarchar +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE AGGREGATE min ( + BASETYPE = mvarchar, + SFUNC = mvarchar_smaller, + STYPE = mvarchar, + SORTOP = '<' +); + +-- B-tree support +CREATE OPERATOR FAMILY icase_ops USING btree; +CREATE OPERATOR FAMILY case_ops USING btree; + +CREATE OPERATOR CLASS mchar_icase_ops +DEFAULT FOR TYPE mchar USING btree FAMILY icase_ops AS + OPERATOR 1 < , + OPERATOR 2 <= , + OPERATOR 3 = , + OPERATOR 4 >= , + OPERATOR 5 > , + FUNCTION 1 mchar_icase_cmp(mchar, mchar), + OPERATOR 1 < (mchar, mvarchar), + OPERATOR 2 <= (mchar, mvarchar), + OPERATOR 3 = (mchar, mvarchar), + OPERATOR 4 >= (mchar, mvarchar), + OPERATOR 5 > (mchar, mvarchar), + FUNCTION 1 mc_mv_icase_cmp(mchar, mvarchar); + +CREATE OPERATOR CLASS mchar_case_ops +FOR TYPE mchar USING btree FAMILY case_ops AS + OPERATOR 1 &< , + OPERATOR 2 &<= , + OPERATOR 3 &= , + OPERATOR 4 &>= , + OPERATOR 5 &> , + FUNCTION 1 mchar_case_cmp(mchar, mchar), + OPERATOR 1 &< (mchar, mvarchar), + OPERATOR 2 &<= (mchar, mvarchar), + OPERATOR 3 &= (mchar, mvarchar), + OPERATOR 4 &>= (mchar, mvarchar), + OPERATOR 5 &> (mchar, mvarchar), + FUNCTION 1 mc_mv_case_cmp(mchar, mvarchar); + +CREATE OPERATOR CLASS mchar_icase_ops +DEFAULT FOR TYPE mchar USING hash AS + OPERATOR 1 = , + FUNCTION 1 mchar_hash(mchar); + +CREATE OPERATOR CLASS mvarchar_icase_ops +DEFAULT FOR TYPE mvarchar USING btree FAMILY icase_ops AS + OPERATOR 1 < , + OPERATOR 2 <= , + OPERATOR 3 = , + OPERATOR 4 >= , + OPERATOR 5 > , + FUNCTION 1 mvarchar_icase_cmp(mvarchar, mvarchar), + OPERATOR 1 < (mvarchar, mchar), + OPERATOR 2 <= (mvarchar, mchar), + OPERATOR 3 = (mvarchar, mchar), + OPERATOR 4 >= (mvarchar, mchar), + OPERATOR 5 > (mvarchar, mchar), + FUNCTION 1 mv_mc_icase_cmp(mvarchar, mchar); + +CREATE OPERATOR CLASS mvarchar_case_ops +FOR TYPE mvarchar USING btree FAMILY case_ops AS + OPERATOR 1 &< , + OPERATOR 2 &<= , + OPERATOR 3 &= , + OPERATOR 4 &>= , + OPERATOR 5 &> , + FUNCTION 1 mvarchar_case_cmp(mvarchar, mvarchar), + OPERATOR 1 &< (mvarchar, mchar), + OPERATOR 2 &<= (mvarchar, mchar), + OPERATOR 3 &= (mvarchar, mchar), + OPERATOR 4 &>= (mvarchar, mchar), + OPERATOR 5 &> (mvarchar, mchar), + FUNCTION 1 mv_mc_case_cmp(mvarchar, mchar); + +CREATE OPERATOR CLASS mvarchar_icase_ops +DEFAULT FOR TYPE mvarchar USING hash AS + OPERATOR 1 = , + FUNCTION 1 mvarchar_hash(mvarchar); + + +-- Index support for LIKE + +CREATE FUNCTION mchar_pattern_fixed_prefix(internal, internal, internal) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mchar_greaterstring(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE OR REPLACE FUNCTION isfulleq_mchar(mchar, mchar) +RETURNS bool AS 'MODULE_PATHNAME' +LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + +CREATE OR REPLACE FUNCTION fullhash_mchar(mchar) +RETURNS int4 AS 'MODULE_PATHNAME' +LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + + +CREATE OPERATOR == ( + LEFTARG = mchar, + RIGHTARG = mchar, + PROCEDURE = isfulleq_mchar, + COMMUTATOR = '==', + RESTRICT = eqsel, + JOIN = eqjoinsel, + HASHES +); + +CREATE OPERATOR CLASS mchar_fill_ops + FOR TYPE mchar USING hash AS + OPERATOR 1 ==, + FUNCTION 1 fullhash_mchar(mchar); + +CREATE OR REPLACE FUNCTION isfulleq_mvarchar(mvarchar, mvarchar) +RETURNS bool AS 'MODULE_PATHNAME' +LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + +CREATE OR REPLACE FUNCTION fullhash_mvarchar(mvarchar) +RETURNS int4 AS 'MODULE_PATHNAME' +LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + + +CREATE OPERATOR == ( + LEFTARG = mvarchar, + RIGHTARG = mvarchar, + PROCEDURE = isfulleq_mvarchar, + COMMUTATOR = '==', + RESTRICT = eqsel, + JOIN = eqjoinsel, + HASHES +); + +CREATE OPERATOR CLASS mvarchar_fill_ops + FOR TYPE mvarchar USING hash AS + OPERATOR 1 ==, + FUNCTION 1 fullhash_mvarchar(mvarchar); + +CREATE FUNCTION similar_to_escape(mchar) + RETURNS mchar + AS 'MODULE_PATHNAME', 'mchar_similar_escape' + LANGUAGE C IMMUTABLE; + +CREATE FUNCTION similar_to_escape(mchar, mchar) + RETURNS mchar + AS 'MODULE_PATHNAME', 'mchar_similar_escape' + LANGUAGE C IMMUTABLE; + +CREATE FUNCTION similar_to_escape(mvarchar) + RETURNS mvarchar + AS 'MODULE_PATHNAME', 'mvarchar_similar_escape' + LANGUAGE C IMMUTABLE; + +CREATE FUNCTION similar_to_escape(mvarchar, mvarchar) + RETURNS mvarchar + AS 'MODULE_PATHNAME', 'mvarchar_similar_escape' + LANGUAGE C IMMUTABLE; + diff --git a/contrib/mchar/mchar--unpackaged--2.0.sql b/contrib/mchar/mchar--unpackaged--2.0.sql new file mode 100644 index 00000000000..1acc4ccec1e --- /dev/null +++ b/contrib/mchar/mchar--unpackaged--2.0.sql @@ -0,0 +1,404 @@ +\echo Use "CREATE EXTENSION mchar FROM unpackaged" to load this file. \quit + +-- I/O functions + +ALTER EXTENSION mchar ADD FUNCTION mchartypmod_in(cstring[]); + +ALTER EXTENSION mchar ADD FUNCTION mchartypmod_out(int4); + +ALTER EXTENSION mchar ADD FUNCTION mchar_in(cstring); + +ALTER EXTENSION mchar ADD FUNCTION mchar_out(mchar); + +ALTER EXTENSION mchar ADD FUNCTION mchar_send(mchar); + +ALTER EXTENSION mchar ADD FUNCTION mchar_recv(internal); + +ALTER EXTENSION mchar ADD TYPE mchar; + +ALTER EXTENSION mchar ADD FUNCTION mchar(mchar, integer, boolean); + +ALTER EXTENSION mchar ADD CAST (mchar as mchar); + +ALTER EXTENSION mchar ADD FUNCTION mvarchar_in(cstring); + +ALTER EXTENSION mchar ADD FUNCTION mvarchar_out(mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mvarchar_send(mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mvarchar_recv(internal); + +ALTER EXTENSION mchar ADD TYPE mvarchar; + +ALTER EXTENSION mchar ADD FUNCTION mvarchar(mvarchar, integer, boolean); + +ALTER EXTENSION mchar ADD CAST (mvarchar as mvarchar); + +--Operations and functions + +ALTER EXTENSION mchar ADD FUNCTION length(mchar); + +ALTER EXTENSION mchar ADD FUNCTION upper(mchar); + +ALTER EXTENSION mchar ADD FUNCTION lower(mchar); + +ALTER EXTENSION mchar ADD FUNCTION mchar_hash(mchar); + +ALTER EXTENSION mchar ADD FUNCTION mchar_concat(mchar, mchar); + +ALTER EXTENSION mchar ADD OPERATOR || (mchar, mchar); + +ALTER EXTENSION mchar ADD FUNCTION mchar_like(mchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mchar_notlike(mchar, mvarchar); + +ALTER EXTENSION mchar ADD OPERATOR ~~ (mchar, mvarchar); + +ALTER EXTENSION mchar ADD OPERATOR !~~ (mchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mchar_regexeq(mchar, mchar); + +ALTER EXTENSION mchar ADD FUNCTION mchar_regexne(mchar, mchar); + +ALTER EXTENSION mchar ADD OPERATOR ~ (mchar, mchar); + +ALTER EXTENSION mchar ADD OPERATOR !~ (mchar, mchar); + +ALTER EXTENSION mchar ADD FUNCTION similar_escape(mchar, mchar); + +ALTER EXTENSION mchar ADD FUNCTION length(mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION upper(mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION lower(mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mvarchar_hash(mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mvarchar_concat(mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD OPERATOR || (mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mvarchar_like(mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION like_escape(mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mvarchar_notlike(mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD OPERATOR ~~ (mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD OPERATOR !~~ (mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mvarchar_regexeq(mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mvarchar_regexne(mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD OPERATOR ~ (mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD OPERATOR !~ (mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION similar_escape(mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION substr (mchar, int4); + +ALTER EXTENSION mchar ADD FUNCTION substr (mchar, int4, int4); + +ALTER EXTENSION mchar ADD FUNCTION substr (mvarchar, int4); + +ALTER EXTENSION mchar ADD FUNCTION substr (mvarchar, int4, int4); + +-- Comparing +-- MCHAR + +ALTER EXTENSION mchar ADD FUNCTION mchar_icase_cmp(mchar, mchar); + +ALTER EXTENSION mchar ADD FUNCTION mchar_icase_eq(mchar, mchar); + +ALTER EXTENSION mchar ADD FUNCTION mchar_icase_ne(mchar, mchar); + +ALTER EXTENSION mchar ADD FUNCTION mchar_icase_lt(mchar, mchar); + +ALTER EXTENSION mchar ADD FUNCTION mchar_icase_le(mchar, mchar); + +ALTER EXTENSION mchar ADD FUNCTION mchar_icase_gt(mchar, mchar); + +ALTER EXTENSION mchar ADD FUNCTION mchar_icase_ge(mchar, mchar); + +ALTER EXTENSION mchar ADD OPERATOR < (mchar, mchar); + +ALTER EXTENSION mchar ADD OPERATOR > (mchar, mchar); + +ALTER EXTENSION mchar ADD OPERATOR <= (mchar, mchar); + +ALTER EXTENSION mchar ADD OPERATOR >= (mchar, mchar); + +ALTER EXTENSION mchar ADD OPERATOR = (mchar, mchar); + +ALTER EXTENSION mchar ADD OPERATOR <> (mchar, mchar); + +ALTER EXTENSION mchar ADD FUNCTION mchar_case_cmp(mchar, mchar); + +ALTER EXTENSION mchar ADD FUNCTION mchar_case_eq(mchar, mchar); + +ALTER EXTENSION mchar ADD FUNCTION mchar_case_ne(mchar, mchar); + +ALTER EXTENSION mchar ADD FUNCTION mchar_case_lt(mchar, mchar); + +ALTER EXTENSION mchar ADD FUNCTION mchar_case_le(mchar, mchar); + +ALTER EXTENSION mchar ADD FUNCTION mchar_case_gt(mchar, mchar); + +ALTER EXTENSION mchar ADD FUNCTION mchar_case_ge(mchar, mchar); + + +ALTER EXTENSION mchar ADD OPERATOR &< (mchar, mchar); + +ALTER EXTENSION mchar ADD OPERATOR &> (mchar, mchar); + +ALTER EXTENSION mchar ADD OPERATOR &<= (mchar, mchar); + +ALTER EXTENSION mchar ADD OPERATOR &>= (mchar, mchar); + +ALTER EXTENSION mchar ADD OPERATOR &= (mchar, mchar); + +ALTER EXTENSION mchar ADD OPERATOR &<> (mchar, mchar); + +--MVARCHAR + +ALTER EXTENSION mchar ADD FUNCTION mvarchar_icase_cmp(mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mvarchar_icase_eq(mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mvarchar_icase_ne(mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mvarchar_icase_lt(mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mvarchar_icase_le(mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mvarchar_icase_gt(mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mvarchar_icase_ge(mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD OPERATOR < (mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD OPERATOR > (mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD OPERATOR <= (mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD OPERATOR >= (mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD OPERATOR = (mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD OPERATOR <> (mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mvarchar_case_cmp(mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mvarchar_case_eq(mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mvarchar_case_ne(mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mvarchar_case_lt(mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mvarchar_case_le(mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mvarchar_case_gt(mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mvarchar_case_ge(mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD OPERATOR &< (mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD OPERATOR &> (mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD OPERATOR &<= (mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD OPERATOR &>= (mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD OPERATOR &= (mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD OPERATOR &<> (mvarchar, mvarchar); + +-- MCHAR <> MVARCHAR + +ALTER EXTENSION mchar ADD FUNCTION mc_mv_icase_cmp(mchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mc_mv_icase_eq(mchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mc_mv_icase_ne(mchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mc_mv_icase_lt(mchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mc_mv_icase_le(mchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mc_mv_icase_gt(mchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mc_mv_icase_ge(mchar, mvarchar); + +ALTER EXTENSION mchar ADD OPERATOR < (mchar, mvarchar); + +ALTER EXTENSION mchar ADD OPERATOR > (mchar, mvarchar); + +ALTER EXTENSION mchar ADD OPERATOR <= (mchar, mvarchar); + +ALTER EXTENSION mchar ADD OPERATOR >= (mchar, mvarchar); + +ALTER EXTENSION mchar ADD OPERATOR = (mchar, mvarchar); + +ALTER EXTENSION mchar ADD OPERATOR <> (mchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mc_mv_case_cmp(mchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mc_mv_case_eq(mchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mc_mv_case_ne(mchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mc_mv_case_lt(mchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mc_mv_case_le(mchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mc_mv_case_gt(mchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mc_mv_case_ge(mchar, mvarchar); + +ALTER EXTENSION mchar ADD OPERATOR &< (mchar, mvarchar); + +ALTER EXTENSION mchar ADD OPERATOR &> (mchar, mvarchar); + +ALTER EXTENSION mchar ADD OPERATOR &<= (mchar, mvarchar); + +ALTER EXTENSION mchar ADD OPERATOR &>= (mchar, mvarchar); + +ALTER EXTENSION mchar ADD OPERATOR &= (mchar, mvarchar); + +ALTER EXTENSION mchar ADD OPERATOR &<> (mchar, mvarchar); + +-- MVARCHAR <> MCHAR + +ALTER EXTENSION mchar ADD FUNCTION mv_mc_icase_cmp(mvarchar, mchar); + +ALTER EXTENSION mchar ADD FUNCTION mv_mc_icase_eq(mvarchar, mchar); + +ALTER EXTENSION mchar ADD FUNCTION mv_mc_icase_ne(mvarchar, mchar); + +ALTER EXTENSION mchar ADD FUNCTION mv_mc_icase_lt(mvarchar, mchar); + +ALTER EXTENSION mchar ADD FUNCTION mv_mc_icase_le(mvarchar, mchar); + +ALTER EXTENSION mchar ADD FUNCTION mv_mc_icase_gt(mvarchar, mchar); + +ALTER EXTENSION mchar ADD FUNCTION mv_mc_icase_ge(mvarchar, mchar); + +ALTER EXTENSION mchar ADD OPERATOR < (mvarchar, mchar); + +ALTER EXTENSION mchar ADD OPERATOR > (mvarchar, mchar); + +ALTER EXTENSION mchar ADD OPERATOR <= (mvarchar, mchar); + +ALTER EXTENSION mchar ADD OPERATOR >= (mvarchar, mchar); + +ALTER EXTENSION mchar ADD OPERATOR = (mvarchar, mchar); + +ALTER EXTENSION mchar ADD OPERATOR <> (mvarchar, mchar); + +ALTER EXTENSION mchar ADD FUNCTION mv_mc_case_cmp(mvarchar, mchar); + +ALTER EXTENSION mchar ADD FUNCTION mv_mc_case_eq(mvarchar, mchar); + +ALTER EXTENSION mchar ADD FUNCTION mv_mc_case_ne(mvarchar, mchar); + +ALTER EXTENSION mchar ADD FUNCTION mv_mc_case_lt(mvarchar, mchar); + +ALTER EXTENSION mchar ADD FUNCTION mv_mc_case_le(mvarchar, mchar); + +ALTER EXTENSION mchar ADD FUNCTION mv_mc_case_gt(mvarchar, mchar); + +ALTER EXTENSION mchar ADD FUNCTION mv_mc_case_ge(mvarchar, mchar); + +ALTER EXTENSION mchar ADD OPERATOR &< (mvarchar, mchar); + +ALTER EXTENSION mchar ADD OPERATOR &> (mvarchar, mchar); + +ALTER EXTENSION mchar ADD OPERATOR &<= (mvarchar, mchar); + +ALTER EXTENSION mchar ADD OPERATOR &>= (mvarchar, mchar); + +ALTER EXTENSION mchar ADD OPERATOR &= (mvarchar, mchar); + +ALTER EXTENSION mchar ADD OPERATOR &<> (mvarchar, mchar); + +-- MCHAR - VARCHAR operations + +ALTER EXTENSION mchar ADD FUNCTION mchar_mvarchar_concat(mchar, mvarchar); + +ALTER EXTENSION mchar ADD OPERATOR || (mchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mvarchar_mchar_concat(mvarchar, mchar); + +ALTER EXTENSION mchar ADD OPERATOR || (mvarchar, mchar); + +ALTER EXTENSION mchar ADD FUNCTION mvarchar_mchar(mvarchar, integer, boolean); + +ALTER EXTENSION mchar ADD CAST (mvarchar as mchar); + +ALTER EXTENSION mchar ADD FUNCTION mchar_mvarchar(mchar, integer, boolean); + +ALTER EXTENSION mchar ADD CAST (mchar as mvarchar); + +-- Aggregates + +ALTER EXTENSION mchar ADD FUNCTION mchar_larger(mchar, mchar); + +ALTER EXTENSION mchar ADD AGGREGATE max (mchar); + +ALTER EXTENSION mchar ADD FUNCTION mchar_smaller(mchar, mchar); + +ALTER EXTENSION mchar ADD AGGREGATE min (mchar); + +ALTER EXTENSION mchar ADD FUNCTION mvarchar_larger(mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD AGGREGATE max (mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION mvarchar_smaller(mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD AGGREGATE min (mvarchar); + +-- B-tree support +ALTER EXTENSION mchar ADD OPERATOR FAMILY icase_ops USING btree; + +ALTER EXTENSION mchar ADD OPERATOR FAMILY case_ops USING btree; + +ALTER EXTENSION mchar ADD OPERATOR CLASS mchar_icase_ops USING btree; + +ALTER EXTENSION mchar ADD OPERATOR CLASS mchar_case_ops USING btree; + +ALTER EXTENSION mchar ADD OPERATOR CLASS mchar_icase_ops USING hash; + +ALTER EXTENSION mchar ADD OPERATOR CLASS mvarchar_icase_ops USING btree; + +ALTER EXTENSION mchar ADD OPERATOR CLASS mvarchar_case_ops USING btree; + +ALTER EXTENSION mchar ADD OPERATOR CLASS mvarchar_icase_ops USING hash; + + +-- Index support for LIKE + +--mchar_pattern_fixed_prefix could be with wrong number of arguments +ALTER EXTENSION mchar ADD FUNCTION mchar_pattern_fixed_prefix; + +ALTER EXTENSION mchar ADD FUNCTION mchar_greaterstring(internal); + +ALTER EXTENSION mchar ADD FUNCTION isfulleq_mchar(mchar, mchar); + +ALTER EXTENSION mchar ADD FUNCTION fullhash_mchar(mchar); + +ALTER EXTENSION mchar ADD OPERATOR == (mchar, mchar); + +ALTER EXTENSION mchar ADD OPERATOR CLASS mchar_fill_ops USING hash; + +ALTER EXTENSION mchar ADD FUNCTION isfulleq_mvarchar(mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD FUNCTION fullhash_mvarchar(mvarchar); + +ALTER EXTENSION mchar ADD OPERATOR == (mvarchar, mvarchar); + +ALTER EXTENSION mchar ADD OPERATOR CLASS mvarchar_fill_ops USING hash; + + diff --git a/contrib/mchar/mchar.control b/contrib/mchar/mchar.control new file mode 100644 index 00000000000..02668a5d617 --- /dev/null +++ b/contrib/mchar/mchar.control @@ -0,0 +1,6 @@ +# mchar extension +comment = 'SQL Server text type' +default_version = '2.2.1' +module_pathname = '$libdir/mchar' +relocatable = true +trusted = true diff --git a/contrib/mchar/mchar.h b/contrib/mchar/mchar.h new file mode 100644 index 00000000000..a88a0e1eb76 --- /dev/null +++ b/contrib/mchar/mchar.h @@ -0,0 +1,63 @@ +#ifndef __MCHAR_H__ +#define __MCHAR_H__ + +#include "postgres.h" +#include "mb/pg_wchar.h" +#include "utils/builtins.h" +#include "unicode/uchar.h" +#include "unicode/ustring.h" + +typedef struct { + int32 len; + int32 typmod; + UChar data[1]; +} MChar; + +#define MCHARHDRSZ offsetof(MChar, data) +#define MCHARLENGTH(m) ( VARSIZE(m)-MCHARHDRSZ ) +#define UCHARLENGTH(m) ( MCHARLENGTH(m)/sizeof(UChar) ) + +#define DatumGetMChar(m) ((MChar*)DatumGetPointer(m)) +#define MCharGetDatum(m) PointerGetDatum(m) + +#define PG_GETARG_MCHAR(n) DatumGetMChar(PG_DETOAST_DATUM(PG_GETARG_DATUM(n))) +#define PG_GETARG_MCHAR_COPY(n) DatumGetMChar(PG_DETOAST_DATUM_COPY(PG_GETARG_DATUM(n))) + +#define PG_RETURN_MCHAR(m) PG_RETURN_POINTER(m) + +typedef struct { + int32 len; + UChar data[1]; +} MVarChar; + +#define MVARCHARHDRSZ offsetof(MVarChar, data) +#define MVARCHARLENGTH(m) ( VARSIZE(m)-MVARCHARHDRSZ ) +#define UVARCHARLENGTH(m) ( MVARCHARLENGTH(m)/sizeof(UChar) ) + +#define DatumGetMVarChar(m) ((MVarChar*)DatumGetPointer(m)) +#define MVarCharGetDatum(m) PointerGetDatum(m) + +#define PG_GETARG_MVARCHAR(n) DatumGetMVarChar(PG_DETOAST_DATUM(PG_GETARG_DATUM(n))) +#define PG_GETARG_MVARCHAR_COPY(n) DatumGetMVarChar(PG_DETOAST_DATUM_COPY(PG_GETARG_DATUM(n))) + +#define PG_RETURN_MVARCHAR(m) PG_RETURN_POINTER(m) + + +int Char2UChar(const char * src, int srclen, UChar *dst); +int UChar2Char(const UChar * src, int srclen, char *dst); +int UChar2Wchar(UChar * src, int srclen, pg_wchar *dst); +int UCharCompare(UChar * a, int alen, UChar *b, int blen); +int UCharCaseCompare(UChar * a, int alen, UChar *b, int blen); + +void FillWhiteSpace( UChar *dst, int n ); + +int lengthWithoutSpaceVarChar(MVarChar *m); +int lengthWithoutSpaceChar(MChar *m); + +extern Datum mchar_hash(PG_FUNCTION_ARGS); +extern Datum mvarchar_hash(PG_FUNCTION_ARGS); + +int m_isspace(UChar c); /* is == ' ' */ + +Datum hash_uchar( UChar *s, int len ); +#endif diff --git a/contrib/mchar/mchar_io.c b/contrib/mchar/mchar_io.c new file mode 100644 index 00000000000..d6c2ac7d393 --- /dev/null +++ b/contrib/mchar/mchar_io.c @@ -0,0 +1,403 @@ +#include "mchar.h" +#include "mb/pg_wchar.h" +#include "fmgr.h" +#include "libpq/pqformat.h" +#include "nodes/nodeFuncs.h" +#include "nodes/supportnodes.h" +#include <utils/array.h> + +#ifdef PG_MODULE_MAGIC +PG_MODULE_MAGIC; +#endif + +PG_FUNCTION_INFO_V1(mchar_in); +Datum mchar_in(PG_FUNCTION_ARGS); +PG_FUNCTION_INFO_V1(mchar_out); +Datum mchar_out(PG_FUNCTION_ARGS); +PG_FUNCTION_INFO_V1(mchar); +Datum mchar(PG_FUNCTION_ARGS); + +PG_FUNCTION_INFO_V1(mvarchar_in); +Datum mvarchar_in(PG_FUNCTION_ARGS); +PG_FUNCTION_INFO_V1(mvarchar_out); +Datum mvarchar_out(PG_FUNCTION_ARGS); +PG_FUNCTION_INFO_V1(mvarchar); +Datum mvarchar(PG_FUNCTION_ARGS); + +PG_FUNCTION_INFO_V1(mvarchar_support); +Datum varchar_support(PG_FUNCTION_ARGS); + +PG_FUNCTION_INFO_V1(mchartypmod_in); +Datum mchartypmod_in(PG_FUNCTION_ARGS); +Datum +mchartypmod_in(PG_FUNCTION_ARGS) { + ArrayType *ta = PG_GETARG_ARRAYTYPE_P(0); + int32 *tl; + int n; + + tl = ArrayGetIntegerTypmods(ta, &n); + + if (n != 1) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid type modifier"))); + if (*tl < 1) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("length for type mchar/mvarchar must be at least 1"))); + + return *tl; +} + +PG_FUNCTION_INFO_V1(mchartypmod_out); +Datum mchartypmod_out(PG_FUNCTION_ARGS); +Datum +mchartypmod_out(PG_FUNCTION_ARGS) { + int32 typmod = PG_GETARG_INT32(0); + char *res = (char *) palloc(64); + + if (typmod >0) + snprintf(res, 64, "(%d)", (int) (typmod)); + else + *res = '\0'; + + PG_RETURN_CSTRING( res ); +} + +static void +mchar_strip( MChar * m, int atttypmod ) { + int maxlen; + + if ( atttypmod<=0 ) { + atttypmod =-1; + } else { + int charlen = u_countChar32( m->data, UCHARLENGTH(m) ); + + if ( charlen > atttypmod ) { + int i=0; + U16_FWD_N( m->data, i, UCHARLENGTH(m), atttypmod); + SET_VARSIZE( m, sizeof(UChar) * i + MCHARHDRSZ ); + } + } + + m->typmod = atttypmod; + + maxlen = UCHARLENGTH(m); + while( maxlen>0 && m_isspace( m->data[ maxlen-1 ] ) ) + maxlen--; + + SET_VARSIZE(m, sizeof(UChar) * maxlen + MCHARHDRSZ); +} + + +Datum +mchar_in(PG_FUNCTION_ARGS) { + char *s = PG_GETARG_CSTRING(0); +#ifdef NOT_USED + Oid typelem = PG_GETARG_OID(1); +#endif + int32 atttypmod = PG_GETARG_INT32(2); + MChar *result; + int32 slen = strlen(s), rlen; + + pg_verifymbstr(s, slen, false); + + result = (MChar*)palloc( MCHARHDRSZ + slen * sizeof(UChar) * 4 /* upper limit of length */ ); + rlen = Char2UChar( s, slen, result->data ); + SET_VARSIZE(result, sizeof(UChar) * rlen + MCHARHDRSZ); + + mchar_strip(result, atttypmod); + + PG_RETURN_MCHAR(result); +} + +Datum +mchar_out(PG_FUNCTION_ARGS) { + MChar *in = PG_GETARG_MCHAR(0); + char *out; + size_t size, inlen = UCHARLENGTH(in); + size_t charlen = u_countChar32(in->data, inlen); + + Assert( in->typmod < 0 || charlen<=in->typmod ); + size = ( in->typmod < 0 ) ? inlen : in->typmod; + size *= pg_database_encoding_max_length(); + + out = (char*)palloc( size+1 ); + size = UChar2Char( in->data, inlen, out ); + + if ( in->typmod>0 && charlen < in->typmod ) { + memset( out+size, ' ', in->typmod - charlen); + size += in->typmod - charlen; + } + + out[size] = '\0'; + + PG_FREE_IF_COPY(in,0); + + PG_RETURN_CSTRING(out); +} + +Datum +mchar(PG_FUNCTION_ARGS) { + MChar *source = PG_GETARG_MCHAR(0); + MChar *result; + int32 typmod = PG_GETARG_INT32(1); +#ifdef NOT_USED + bool isExplicit = PG_GETARG_BOOL(2); +#endif + + result = palloc( VARSIZE(source) ); + memcpy( result, source, VARSIZE(source) ); + PG_FREE_IF_COPY(source,0); + + mchar_strip(result, typmod); + + PG_RETURN_MCHAR(result); +} + +Datum +mvarchar_in(PG_FUNCTION_ARGS) { + char *s = PG_GETARG_CSTRING(0); +#ifdef NOT_USED + Oid typelem = PG_GETARG_OID(1); +#endif + int32 atttypmod = PG_GETARG_INT32(2); + MVarChar *result; + int32 slen = strlen(s), rlen; + + pg_verifymbstr(s, slen, false); + + result = (MVarChar*)palloc( MVARCHARHDRSZ + slen * sizeof(UChar) * 2 /* upper limit of length */ ); + rlen = Char2UChar( s, slen, result->data ); + SET_VARSIZE(result, sizeof(UChar) * rlen + MVARCHARHDRSZ); + + if ( atttypmod > 0 && atttypmod < u_countChar32(result->data, UVARCHARLENGTH(result)) ) + elog(ERROR,"value too long for type mvarchar(%d)", atttypmod); + + PG_RETURN_MVARCHAR(result); +} + +Datum +mvarchar_out(PG_FUNCTION_ARGS) { + MVarChar *in = PG_GETARG_MVARCHAR(0); + char *out; + size_t size = UVARCHARLENGTH(in); + + size *= pg_database_encoding_max_length(); + + out = (char*)palloc( size+1 ); + size = UChar2Char( in->data, UVARCHARLENGTH(in), out ); + + out[size] = '\0'; + + PG_FREE_IF_COPY(in,0); + + PG_RETURN_CSTRING(out); +} + +static void +mvarchar_strip(MVarChar *m, int atttypmod) { + int charlen = u_countChar32(m->data, UVARCHARLENGTH(m)); + + if ( atttypmod>=0 && atttypmod < charlen ) { + int i=0; + U16_FWD_N( m->data, i, charlen, atttypmod); + SET_VARSIZE(m, sizeof(UChar) * i + MVARCHARHDRSZ); + } +} + +Datum +mvarchar(PG_FUNCTION_ARGS) { + MVarChar *source = PG_GETARG_MVARCHAR(0); + MVarChar *result; + int32 typmod = PG_GETARG_INT32(1); + bool isExplicit = PG_GETARG_BOOL(2); + int charlen = u_countChar32(source->data, UVARCHARLENGTH(source)); + + result = palloc( VARSIZE(source) ); + memcpy( result, source, VARSIZE(source) ); + PG_FREE_IF_COPY(source,0); + + if ( typmod>=0 && typmod < charlen ) { + if ( isExplicit ) + mvarchar_strip(result, typmod); + else + elog(ERROR,"value too long for type mvarchar(%d)", typmod); + } + + PG_RETURN_MVARCHAR(result); +} + +PG_FUNCTION_INFO_V1(mvarchar_mchar); +Datum mvarchar_mchar(PG_FUNCTION_ARGS); +Datum +mvarchar_mchar(PG_FUNCTION_ARGS) { + MVarChar *source = PG_GETARG_MVARCHAR(0); + MChar *result; + int32 typmod = PG_GETARG_INT32(1); +#ifdef NOT_USED + bool isExplicit = PG_GETARG_BOOL(2); +#endif + + result = palloc( MVARCHARLENGTH(source) + MCHARHDRSZ ); + SET_VARSIZE(result, MVARCHARLENGTH(source) + MCHARHDRSZ); + memcpy( result->data, source->data, MVARCHARLENGTH(source)); + + PG_FREE_IF_COPY(source,0); + + mchar_strip( result, typmod ); + + PG_RETURN_MCHAR(result); +} + +PG_FUNCTION_INFO_V1(mchar_mvarchar); +Datum mchar_mvarchar(PG_FUNCTION_ARGS); +Datum +mchar_mvarchar(PG_FUNCTION_ARGS) { + MChar *source = PG_GETARG_MCHAR(0); + MVarChar *result; + int32 typmod = PG_GETARG_INT32(1); + int32 scharlen = u_countChar32(source->data, UCHARLENGTH(source)); + int32 curlen = 0, maxcharlen; +#ifdef NOT_USED + bool isExplicit = PG_GETARG_BOOL(2); +#endif + + maxcharlen = (source->typmod > 0) ? source->typmod : scharlen; + + result = palloc( MVARCHARHDRSZ + sizeof(UChar) * 2 * maxcharlen ); + + curlen = UCHARLENGTH( source ); + if ( curlen > 0 ) + memcpy( result->data, source->data, MCHARLENGTH(source) ); + if ( source->typmod > 0 && scharlen < source->typmod ) { + FillWhiteSpace( result->data + curlen, source->typmod-scharlen ); + curlen += source->typmod-scharlen; + } + SET_VARSIZE(result, MVARCHARHDRSZ + curlen *sizeof(UChar)); + + PG_FREE_IF_COPY(source,0); + + mvarchar_strip( result, typmod ); + + PG_RETURN_MCHAR(result); +} + +PG_FUNCTION_INFO_V1(mchar_send); +Datum mchar_send(PG_FUNCTION_ARGS); +Datum +mchar_send(PG_FUNCTION_ARGS) { + MChar *in = PG_GETARG_MCHAR(0); + size_t inlen = UCHARLENGTH(in); + size_t charlen = u_countChar32(in->data, inlen); + StringInfoData buf; + + Assert( in->typmod < 0 || charlen<=in->typmod ); + + pq_begintypsend(&buf); + pq_sendbytes(&buf, (char*)in->data, inlen * sizeof(UChar) ); + + if ( in->typmod>0 && charlen < in->typmod ) { + int nw = in->typmod - charlen; + UChar *white = palloc( sizeof(UChar) * nw ); + + FillWhiteSpace( white, nw ); + pq_sendbytes(&buf, (char*)white, sizeof(UChar) * nw); + pfree(white); + } + + PG_FREE_IF_COPY(in,0); + + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); +} + +PG_FUNCTION_INFO_V1(mchar_recv); +Datum mchar_recv(PG_FUNCTION_ARGS); +Datum +mchar_recv(PG_FUNCTION_ARGS) { + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + MChar *res; + int nbytes; +#ifdef NOT_USED + Oid typelem = PG_GETARG_OID(1); +#endif + int32 atttypmod = PG_GETARG_INT32(2); + + nbytes = buf->len - buf->cursor; + res = (MChar*)palloc( nbytes + MCHARHDRSZ ); + res->len = nbytes + MCHARHDRSZ; + res->typmod = -1; + SET_VARSIZE(res, res->len); + pq_copymsgbytes(buf, (char*)res->data, nbytes); + + mchar_strip( res, atttypmod ); + + PG_RETURN_MCHAR(res); +} + +PG_FUNCTION_INFO_V1(mvarchar_send); +Datum mvarchar_send(PG_FUNCTION_ARGS); +Datum +mvarchar_send(PG_FUNCTION_ARGS) { + MVarChar *in = PG_GETARG_MVARCHAR(0); + size_t inlen = UVARCHARLENGTH(in); + StringInfoData buf; + + pq_begintypsend(&buf); + pq_sendbytes(&buf, (char*)in->data, inlen * sizeof(UChar) ); + + PG_FREE_IF_COPY(in,0); + + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); +} + +PG_FUNCTION_INFO_V1(mvarchar_recv); +Datum mvarchar_recv(PG_FUNCTION_ARGS); +Datum +mvarchar_recv(PG_FUNCTION_ARGS) { + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + MVarChar *res; + int nbytes; +#ifdef NOT_USED + Oid typelem = PG_GETARG_OID(1); +#endif + int32 atttypmod = PG_GETARG_INT32(2); + + nbytes = buf->len - buf->cursor; + res = (MVarChar*)palloc( nbytes + MVARCHARHDRSZ ); + res->len = nbytes + MVARCHARHDRSZ; + SET_VARSIZE(res, res->len); + pq_copymsgbytes(buf, (char*)res->data, nbytes); + + mvarchar_strip( res, atttypmod ); + + PG_RETURN_MVARCHAR(res); +} + +Datum +mvarchar_support(PG_FUNCTION_ARGS) +{ + Node *node = (Node *) PG_GETARG_POINTER(0); + Node *ret = NULL; + + if (IsA(node, SupportRequestSimplify)) + { + SupportRequestSimplify *req = (SupportRequestSimplify *) node; + FuncExpr *expr = req->fcall; + Node *typmodnode; + + typmodnode = (Node *) lsecond(expr->args); + + if (IsA(typmodnode, Const) && !((Const *) typmodnode)->constisnull) + { + Node *source = (Node *) linitial(expr->args); + int32 source_typmod = exprTypmod(source); + int32 req_typemod = DatumGetInt32(((Const *) typmodnode)->constvalue); + + if (req_typemod < 0 || (source_typmod >= 0 && source_typmod <= req_typemod)) + ret = relabel_to_typmod(source, req_typemod); + } + } + + PG_RETURN_POINTER(ret); +} diff --git a/contrib/mchar/mchar_like.c b/contrib/mchar/mchar_like.c new file mode 100644 index 00000000000..1f83fb37864 --- /dev/null +++ b/contrib/mchar/mchar_like.c @@ -0,0 +1,929 @@ +#include "mchar.h" +#include "mb/pg_wchar.h" + +#include "catalog/pg_collation.h" +#include "utils/selfuncs.h" +#include "nodes/primnodes.h" +#include "nodes/makefuncs.h" +#include "nodes/supportnodes.h" +#include "regex/regex.h" + +/* +** Originally written by Rich $alz, mirror!rs, Wed Nov 26 19:03:17 EST 1986. +** Rich $alz is now <rsalz@bbn.com>. +** Special thanks to Lars Mathiesen <thorinn@diku.dk> for the LABORT code. +** +** This code was shamelessly stolen from the "pql" code by myself and +** slightly modified :) +** +** All references to the word "star" were replaced by "percent" +** All references to the word "wild" were replaced by "like" +** +** All the nice shell RE matching stuff was replaced by just "_" and "%" +** +** As I don't have a copy of the SQL standard handy I wasn't sure whether +** to leave in the '\' escape character handling. +** +** Keith Parks. <keith@mtcc.demon.co.uk> +** +** SQL92 lets you specify the escape character by saying +** LIKE <pattern> ESCAPE <escape character>. We are a small operation +** so we force you to use '\'. - ay 7/95 +** +** Now we have the like_escape() function that converts patterns with +** any specified escape character (or none at all) to the internal +** default escape character, which is still '\'. - tgl 9/2000 +** +** The code is rewritten to avoid requiring null-terminated strings, +** which in turn allows us to leave out some memcpy() operations. +** This code should be faster and take less memory, but no promises... +** - thomas 2000-08-06 +** +** Adopted for UTF-16 by teodor +*/ + +#define LIKE_TRUE 1 +#define LIKE_FALSE 0 +#define LIKE_ABORT (-1) + + +static int +uchareq(UChar *p1, UChar *p2) { + int l1=0, l2=0; + /* + * Count length of char: + * We suppose that string is correct!! + */ + U16_FWD_1(p1, l1, 2); + U16_FWD_1(p2, l2, 2); + + return (UCharCaseCompare(p1, l1, p2, l2)==0) ? 1 : 0; +} + +#define NextChar(p, plen) \ + do { \ + int __l = 0; \ + U16_FWD_1((p), __l, (plen));\ + (p) +=__l; \ + (plen) -=__l; \ + } while(0) + +#define CopyAdvChar(dst, src, srclen) \ + do { \ + int __l = 0; \ + U16_FWD_1((src), __l, (srclen));\ + (srclen) -= __l; \ + while (__l-- > 0) \ + *(dst)++ = *(src)++; \ + } while (0) + + +static UChar UCharPercent = 0; +static UChar UCharBackSlesh = 0; +static UChar UCharUnderLine = 0; +static UChar UCharStar = 0; +static UChar UCharDotDot = 0; +static UChar UCharUp = 0; +static UChar UCharLBracket = 0; +static UChar UCharQ = 0; +static UChar UCharRBracket = 0; +static UChar UCharDollar = 0; +static UChar UCharDot = 0; +static UChar UCharLFBracket = 0; +static UChar UCharRFBracket = 0; +static UChar UCharQuote = 0; +static UChar UCharSpace = 0; +static UChar UCharOne = 0; +static UChar UCharComma = 0; +static UChar UCharLQBracket = 0; +static UChar UCharRQBracket = 0; + +#define MkUChar(uc, c) do { \ + char __c = (c); \ + u_charsToUChars( &__c, &(uc), 1 ); \ +} while(0) + +#define SET_UCHAR if ( UCharPercent == 0 ) { \ + MkUChar( UCharPercent, '%' ); \ + MkUChar( UCharBackSlesh, '\\' ); \ + MkUChar( UCharUnderLine, '_' ); \ + MkUChar( UCharStar, '*' ); \ + MkUChar( UCharDotDot, ':' ); \ + MkUChar( UCharUp, '^' ); \ + MkUChar( UCharLBracket, '(' ); \ + MkUChar( UCharQ, '?' ); \ + MkUChar( UCharRBracket, ')' ); \ + MkUChar( UCharDollar, '$' ); \ + MkUChar( UCharDot, '.' ); \ + MkUChar( UCharLFBracket, '{' ); \ + MkUChar( UCharRFBracket, '}' ); \ + MkUChar( UCharQuote, '"' ); \ + MkUChar( UCharSpace, ' ' ); \ + MkUChar( UCharOne, '1' ); \ + MkUChar( UCharComma, ',' ); \ + MkUChar( UCharLQBracket, '[' ); \ + MkUChar( UCharRQBracket, ']' ); \ + } + +int +m_isspace(UChar c) { + SET_UCHAR; + + return (c == UCharSpace); +} + +static int +MatchUChar(UChar *t, int tlen, UChar *p, int plen) { + SET_UCHAR; + + /* Fast path for match-everything pattern */ + if ((plen == 1) && (*p == UCharPercent)) + return LIKE_TRUE; + + while ((tlen > 0) && (plen > 0)) { + if (*p == UCharBackSlesh) { + /* Next pattern char must match literally, whatever it is */ + NextChar(p, plen); + if ((plen <= 0) || !uchareq(t, p)) + return LIKE_FALSE; + } else if (*p == UCharPercent) { + /* %% is the same as % according to the SQL standard */ + /* Advance past all %'s */ + while ((plen > 0) && (*p == UCharPercent)) + NextChar(p, plen); + /* Trailing percent matches everything. */ + if (plen <= 0) + return LIKE_TRUE; + + /* + * Otherwise, scan for a text position at which we can match the + * rest of the pattern. + */ + while (tlen > 0) { + /* + * Optimization to prevent most recursion: don't recurse + * unless first pattern char might match this text char. + */ + if (uchareq(t, p) || (*p == UCharBackSlesh) || (*p == UCharUnderLine)) { + int matched = MatchUChar(t, tlen, p, plen); + + if (matched != LIKE_FALSE) + return matched; /* TRUE or ABORT */ + } + + NextChar(t, tlen); + } + + /* + * End of text with no match, so no point in trying later places + * to start matching this pattern. + */ + return LIKE_ABORT; + } if ((*p != UCharUnderLine) && !uchareq(t, p)) { + /* + * Not the single-character wildcard and no explicit match? Then + * time to quit... + */ + return LIKE_FALSE; + } + + NextChar(t, tlen); + NextChar(p, plen); + } + + if (tlen > 0) + return LIKE_FALSE; /* end of pattern, but not of text */ + + /* End of input string. Do we have matching pattern remaining? */ + while ((plen > 0) && (*p == UCharPercent)) /* allow multiple %'s at end of + * pattern */ + NextChar(p, plen); + if (plen <= 0) + return LIKE_TRUE; + + /* + * End of text with no match, so no point in trying later places to start + * matching this pattern. + */ + + return LIKE_ABORT; +} + +PG_FUNCTION_INFO_V1( mvarchar_like ); +Datum mvarchar_like( PG_FUNCTION_ARGS ); +Datum +mvarchar_like( PG_FUNCTION_ARGS ) { + MVarChar *str = PG_GETARG_MVARCHAR(0); + MVarChar *pat = PG_GETARG_MVARCHAR(1); + int result; + + result = MatchUChar( str->data, UVARCHARLENGTH(str), pat->data, UVARCHARLENGTH(pat) ); + + PG_FREE_IF_COPY(str,0); + PG_FREE_IF_COPY(pat,1); + + PG_RETURN_BOOL(result == LIKE_TRUE); +} + +PG_FUNCTION_INFO_V1( mvarchar_notlike ); +Datum mvarchar_notlike( PG_FUNCTION_ARGS ); +Datum +mvarchar_notlike( PG_FUNCTION_ARGS ) { + bool res = DatumGetBool( DirectFunctionCall2( + mvarchar_like, + PG_GETARG_DATUM(0), + PG_GETARG_DATUM(1) + )); + PG_RETURN_BOOL( !res ); +} + +/* + * Removes trailing spaces in '111 %' pattern + */ +static UChar * +removeTrailingSpaces( UChar *src, int srclen, int *dstlen, bool *isSpecialLast) { + UChar* dst = src; + UChar *ptr, *dptr, *markptr; + + *dstlen = srclen; + ptr = src + srclen-1; + SET_UCHAR; + + *isSpecialLast = ( srclen > 0 && (u_isspace(*ptr) || *ptr == UCharPercent || *ptr == UCharUnderLine ) ) ? true : false; + while( ptr>=src ) { + if ( *ptr == UCharPercent || *ptr == UCharUnderLine ) { + if ( ptr==src ) + return dst; /* first character */ + + if ( *(ptr-1) == UCharBackSlesh ) + return dst; /* use src as is */ + + if ( u_isspace( *(ptr-1) ) ) { + ptr--; + break; /* % or _ is after space which should be removed */ + } + } else { + return dst; + } + ptr--; + } + + markptr = ptr+1; + dst = (UChar*)palloc( sizeof(UChar) * srclen ); + + /* find last non-space character */ + while( ptr>=src && u_isspace(*ptr) ) + ptr--; + + dptr = dst + (ptr-src+1); + + if ( ptr>=src ) + memcpy( dst, src, sizeof(UChar) * (ptr-src+1) ); + + while( markptr - src < srclen ) { + *dptr = *markptr; + dptr++; + markptr++; + } + + *dstlen = dptr - dst; + return dst; +} + +static UChar* +addTrailingSpace( MChar *src, int *newlen ) { + int scharlen = u_countChar32(src->data, UCHARLENGTH(src)); + + if ( src->typmod > scharlen ) { + UChar *res = (UChar*) palloc( sizeof(UChar) * (UCHARLENGTH(src) + src->typmod) ); + + memcpy( res, src->data, sizeof(UChar) * UCHARLENGTH(src)); + FillWhiteSpace( res+UCHARLENGTH(src), src->typmod - scharlen ); + + *newlen = src->typmod; + + return res; + } else { + *newlen = UCHARLENGTH(src); + return src->data; + } +} + +PG_FUNCTION_INFO_V1( mchar_like ); +Datum mchar_like( PG_FUNCTION_ARGS ); +Datum +mchar_like( PG_FUNCTION_ARGS ) { + MChar *str = PG_GETARG_MCHAR(0); + MVarChar *pat = PG_GETARG_MVARCHAR(1); + int result; + bool isNeedAdd = false; + UChar *cleaned, *filled; + int clen=0, flen=0; + + cleaned = removeTrailingSpaces(pat->data, UVARCHARLENGTH(pat), &clen, &isNeedAdd); + if ( isNeedAdd ) + filled = addTrailingSpace(str, &flen); + else { + filled = str->data; + flen = UCHARLENGTH(str); + } + + result = MatchUChar( filled, flen, cleaned, clen ); + + if ( pat->data != cleaned ) + pfree( cleaned ); + if ( str->data != filled ) + pfree( filled ); + + PG_FREE_IF_COPY(str,0); + PG_FREE_IF_COPY(pat,1); + + PG_RETURN_BOOL(result == LIKE_TRUE); +} + +PG_FUNCTION_INFO_V1( mchar_notlike ); +Datum mchar_notlike( PG_FUNCTION_ARGS ); +Datum +mchar_notlike( PG_FUNCTION_ARGS ) { + bool res = DatumGetInt32( DirectFunctionCall2( + mchar_like, + PG_GETARG_DATUM(0), + PG_GETARG_DATUM(1) + )); + + PG_RETURN_BOOL( !res ); +} + + + +PG_FUNCTION_INFO_V1( mchar_pattern_fixed_prefix ); +Datum mchar_pattern_fixed_prefix( PG_FUNCTION_ARGS ); +Datum +mchar_pattern_fixed_prefix( PG_FUNCTION_ARGS ) { + Const *patt = (Const*)PG_GETARG_POINTER(0); + Pattern_Type ptype = (Pattern_Type)PG_GETARG_INT32(1); + Const **prefix = (Const**)PG_GETARG_POINTER(2); + UChar *spatt; + int32 slen, prefixlen=0, restlen=0, i=0; + MVarChar *sprefix; + MVarChar *srest; + Pattern_Prefix_Status status = Pattern_Prefix_None; + + *prefix = NULL; + + if ( ptype != Pattern_Type_Like ) + PG_RETURN_INT32(Pattern_Prefix_None); + + SET_UCHAR; + + spatt = ((MVarChar*)DatumGetPointer(patt->constvalue))->data; + slen = UVARCHARLENGTH( DatumGetPointer(patt->constvalue) ); + + sprefix = (MVarChar*)palloc( MCHARHDRSZ /*The biggest hdr!! */ + sizeof(UChar) * slen ); + srest = (MVarChar*)palloc( MCHARHDRSZ /*The biggest hdr!! */ + sizeof(UChar) * slen ); + + while( prefixlen < slen && i < slen ) { + if ( spatt[i] == UCharPercent || spatt[i] == UCharUnderLine ) + break; + else if ( spatt[i] == UCharBackSlesh ) { + i++; + if ( i>= slen ) + break; + } + sprefix->data[ prefixlen++ ] = spatt[i++]; + } + + while( prefixlen > 0 ) { + if ( ! u_isspace( sprefix->data[ prefixlen-1 ] ) ) + break; + prefixlen--; + } + + if ( prefixlen == 0 ) + PG_RETURN_INT32(Pattern_Prefix_None); + + for(;i<slen;i++) + srest->data[ restlen++ ] = spatt[i]; + + SET_VARSIZE(sprefix, sizeof(UChar) * prefixlen + MVARCHARHDRSZ); + SET_VARSIZE(srest, sizeof(UChar) * restlen + MVARCHARHDRSZ); + + *prefix = makeConst( patt->consttype, -1, InvalidOid, VARSIZE(sprefix), PointerGetDatum(sprefix), false, false ); + + if ( prefixlen == slen ) /* in LIKE, an empty pattern is an exact match! */ + status = Pattern_Prefix_Exact; + else if ( prefixlen > 0 ) + status = Pattern_Prefix_Partial; + + PG_RETURN_INT32( status ); +} + +static bool +checkCmp( UChar *left, int32 leftlen, UChar *right, int32 rightlen ) { + + return (UCharCaseCompare( left, leftlen, right, rightlen) < 0 ) ? true : false; +} + + +PG_FUNCTION_INFO_V1( mchar_greaterstring ); +Datum mchar_greaterstring( PG_FUNCTION_ARGS ); +Datum +mchar_greaterstring( PG_FUNCTION_ARGS ) { + Const *patt = (Const*)PG_GETARG_POINTER(0); + char *src = (char*)DatumGetPointer( patt->constvalue ); + int dstlen, srclen = VARSIZE(src); + char *dst = palloc( srclen ); + UChar *ptr, *srcptr; + + memcpy( dst, src, srclen ); + + srclen = dstlen = UVARCHARLENGTH( dst ); + ptr = ((MVarChar*)dst)->data; + srcptr = ((MVarChar*)src)->data; + + while( dstlen > 0 ) { + UChar *lastchar = ptr + dstlen - 1; + + if ( !U16_IS_LEAD( *lastchar ) ) { + while( *lastchar<0xffff ) { + + (*lastchar)++; + + if ( ublock_getCode(*lastchar) == UBLOCK_INVALID_CODE || !checkCmp( srcptr, srclen, ptr, dstlen ) ) + continue; + else { + SET_VARSIZE(dst, sizeof(UChar) * dstlen + MVARCHARHDRSZ); + + PG_RETURN_POINTER( makeConst( patt->consttype, -1, + InvalidOid, VARSIZE(dst), PointerGetDatum(dst), false, false ) ); + } + } + } + + dstlen--; + } + + PG_RETURN_POINTER(NULL); +} + +static int +do_like_escape( UChar *pat, int plen, UChar *esc, int elen, UChar *result) { + UChar *p = pat,*e =esc ,*r; + bool afterescape; + + r = result; + SET_UCHAR; + + if ( elen == 0 ) { + /* + * No escape character is wanted. Double any backslashes in the + * pattern to make them act like ordinary characters. + */ + while (plen > 0) { + if (*p == UCharBackSlesh ) + *r++ = UCharBackSlesh; + CopyAdvChar(r, p, plen); + } + } else { + /* + * The specified escape must be only a single character. + */ + NextChar(e, elen); + + if (elen != 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_ESCAPE_SEQUENCE), + errmsg("invalid escape string"), + errhint("Escape string must be empty or one character."))); + + e = esc; + + /* + * If specified escape is '\', just copy the pattern as-is. + */ + if ( *e == UCharBackSlesh ) { + memcpy(result, pat, plen * sizeof(UChar)); + return plen; + } + + /* + * Otherwise, convert occurrences of the specified escape character to + * '\', and double occurrences of '\' --- unless they immediately + * follow an escape character! + */ + afterescape = false; + + while (plen > 0) { + if ( uchareq(p,e) && !afterescape) { + *r++ = UCharBackSlesh; + NextChar(p, plen); + afterescape = true; + } else if ( *p == UCharBackSlesh ) { + *r++ = UCharBackSlesh; + if (!afterescape) + *r++ = UCharBackSlesh; + NextChar(p, plen); + afterescape = false; + } else { + CopyAdvChar(r, p, plen); + afterescape = false; + } + } + } + + return ( r - result ); +} + +PG_FUNCTION_INFO_V1( mvarchar_like_escape ); +Datum mvarchar_like_escape( PG_FUNCTION_ARGS ); +Datum +mvarchar_like_escape( PG_FUNCTION_ARGS ) { + MVarChar *pat = PG_GETARG_MVARCHAR(0); + MVarChar *esc = PG_GETARG_MVARCHAR(1); + MVarChar *result; + + result = (MVarChar*)palloc( MVARCHARHDRSZ + sizeof(UChar)*2*UVARCHARLENGTH(pat) ); + result->len = MVARCHARHDRSZ + do_like_escape( pat->data, UVARCHARLENGTH(pat), + esc->data, UVARCHARLENGTH(esc), + result->data ) * sizeof(UChar); + + SET_VARSIZE(result, result->len); + PG_FREE_IF_COPY(pat,0); + PG_FREE_IF_COPY(esc,1); + + PG_RETURN_MVARCHAR(result); +} + +#define RE_CACHE_SIZE 32 +typedef struct ReCache { + UChar *pattern; + int length; + int flags; + regex_t re; +} ReCache; + +static int num_res = 0; +static ReCache re_array[RE_CACHE_SIZE]; /* cached re's */ +static const int mchar_regex_flavor = REG_ADVANCED | REG_ICASE; + +static regex_t * +URE_compile_and_cache(UChar *text_re, int text_re_len, int cflags) { + pg_wchar *pattern; + size_t pattern_len; + int i; + int regcomp_result; + ReCache re_temp; + char errMsg[128]; + + + for (i = 0; i < num_res; i++) { + if ( re_array[i].length == text_re_len && + re_array[i].flags == cflags && + memcmp(re_array[i].pattern, text_re, sizeof(UChar)*text_re_len) == 0 ) { + + /* Found, move it to front */ + if ( i>0 ) { + re_temp = re_array[i]; + memmove(&re_array[1], &re_array[0], i * sizeof(ReCache)); + re_array[0] = re_temp; + } + + return &re_array[0].re; + } + } + + pattern = (pg_wchar *) palloc((1 + text_re_len) * sizeof(pg_wchar)); + pattern_len = UChar2Wchar(text_re, text_re_len, pattern); + + regcomp_result = pg_regcomp(&re_temp.re, + pattern, + pattern_len, + cflags, + DEFAULT_COLLATION_OID); + pfree( pattern ); + + if (regcomp_result != REG_OKAY) { + pg_regerror(regcomp_result, &re_temp.re, errMsg, sizeof(errMsg)); + ereport(ERROR, + (errcode(ERRCODE_INVALID_REGULAR_EXPRESSION), + errmsg("invalid regular expression: %s", errMsg))); + } + + re_temp.pattern = malloc( text_re_len*sizeof(UChar) ); + if ( re_temp.pattern == NULL ) + elog(ERROR,"Out of memory"); + + memcpy(re_temp.pattern, text_re, text_re_len*sizeof(UChar) ); + re_temp.length = text_re_len; + re_temp.flags = cflags; + + if (num_res >= RE_CACHE_SIZE) { + --num_res; + Assert(num_res < RE_CACHE_SIZE); + pg_regfree(&re_array[num_res].re); + free(re_array[num_res].pattern); + } + + if (num_res > 0) + memmove(&re_array[1], &re_array[0], num_res * sizeof(ReCache)); + + re_array[0] = re_temp; + num_res++; + + return &re_array[0].re; +} + +static bool +URE_compile_and_execute(UChar *pat, int pat_len, UChar *dat, int dat_len, + int cflags, int nmatch, regmatch_t *pmatch) { + pg_wchar *data; + size_t data_len; + int regexec_result; + regex_t *re; + char errMsg[128]; + + data = (pg_wchar *) palloc((1+dat_len) * sizeof(pg_wchar)); + data_len = UChar2Wchar(dat, dat_len, data); + + re = URE_compile_and_cache(pat, pat_len, cflags); + + regexec_result = pg_regexec(re, + data, + data_len, + 0, + NULL, + nmatch, + pmatch, + 0); + pfree(data); + + if (regexec_result != REG_OKAY && regexec_result != REG_NOMATCH) { + /* re failed??? */ + pg_regerror(regexec_result, re, errMsg, sizeof(errMsg)); + ereport(ERROR, + (errcode(ERRCODE_INVALID_REGULAR_EXPRESSION), + errmsg("regular expression failed: %s", errMsg))); + } + + return (regexec_result == REG_OKAY); +} + +PG_FUNCTION_INFO_V1( mchar_regexeq ); +Datum mchar_regexeq( PG_FUNCTION_ARGS ); +Datum +mchar_regexeq( PG_FUNCTION_ARGS ) { + MChar *t = PG_GETARG_MCHAR(0); + MChar *p = PG_GETARG_MCHAR(1); + bool res; + + res = URE_compile_and_execute(p->data, UCHARLENGTH(p), + t->data, UCHARLENGTH(t), + mchar_regex_flavor, + 0, NULL); + PG_FREE_IF_COPY(t, 0); + PG_FREE_IF_COPY(p, 1); + + PG_RETURN_BOOL(res); +} + +PG_FUNCTION_INFO_V1( mchar_regexne ); +Datum mchar_regexne( PG_FUNCTION_ARGS ); +Datum +mchar_regexne( PG_FUNCTION_ARGS ) { + MChar *t = PG_GETARG_MCHAR(0); + MChar *p = PG_GETARG_MCHAR(1); + bool res; + + res = URE_compile_and_execute(p->data, UCHARLENGTH(p), + t->data, UCHARLENGTH(t), + mchar_regex_flavor, + 0, NULL); + PG_FREE_IF_COPY(t, 0); + PG_FREE_IF_COPY(p, 1); + + PG_RETURN_BOOL(!res); +} + +PG_FUNCTION_INFO_V1( mvarchar_regexeq ); +Datum mvarchar_regexeq( PG_FUNCTION_ARGS ); +Datum +mvarchar_regexeq( PG_FUNCTION_ARGS ) { + MVarChar *t = PG_GETARG_MVARCHAR(0); + MVarChar *p = PG_GETARG_MVARCHAR(1); + bool res; + + res = URE_compile_and_execute(p->data, UVARCHARLENGTH(p), + t->data, UVARCHARLENGTH(t), + mchar_regex_flavor, + 0, NULL); + PG_FREE_IF_COPY(t, 0); + PG_FREE_IF_COPY(p, 1); + + PG_RETURN_BOOL(res); +} + +PG_FUNCTION_INFO_V1( mvarchar_regexne ); +Datum mvarchar_regexne( PG_FUNCTION_ARGS ); +Datum +mvarchar_regexne( PG_FUNCTION_ARGS ) { + MVarChar *t = PG_GETARG_MVARCHAR(0); + MVarChar *p = PG_GETARG_MVARCHAR(1); + bool res; + + res = URE_compile_and_execute(p->data, UVARCHARLENGTH(p), + t->data, UVARCHARLENGTH(t), + mchar_regex_flavor, + 0, NULL); + PG_FREE_IF_COPY(t, 0); + PG_FREE_IF_COPY(p, 1); + + PG_RETURN_BOOL(!res); +} + +static int +do_similar_escape(UChar *p, int plen, UChar *e, int elen, UChar *result) { + UChar *r; + bool afterescape = false; + bool incharclass = false; + int nquotes = 0; + + SET_UCHAR; + + if (e==NULL || elen <0 ) { + e = &UCharBackSlesh; + elen = 1; + } else { + if ( elen == 0 ) + e = NULL; + else if ( elen != 1) + ereport(ERROR, + (errcode(ERRCODE_INVALID_ESCAPE_SEQUENCE), + errmsg("invalid escape string"), + errhint("Escape string must be empty or one character."))); + } + + /* + * Look explanation of following in ./utils/adt/regexp.c + */ + r = result; + + *r++ = UCharUp; + *r++ = UCharLBracket; + *r++ = UCharQ; + *r++ = UCharDotDot; + + while( plen>0 ) { + UChar pchar = *p; + + if (afterescape) + { + if (pchar == UCharQuote && !incharclass) /* escape-double-quote? */ + { + if (nquotes == 0) + { + *r++ = UCharRBracket; + *r++ = UCharLFBracket; + *r++ = UCharOne; + *r++ = UCharComma; + *r++ = UCharOne; + *r++ = UCharRFBracket; + *r++ = UCharQ; + *r++ = UCharLBracket; + } + else if (nquotes == 1) + { + *r++ = UCharRBracket; + *r++ = UCharLFBracket; + *r++ = UCharOne; + *r++ = UCharComma; + *r++ = UCharOne; + *r++ = UCharRFBracket; + *r++ = UCharLBracket; + *r++ = UCharQ; + *r++ = UCharDotDot; + } + else + ereport(ERROR, + (errcode(ERRCODE_INVALID_USE_OF_ESCAPE_CHARACTER), + errmsg("SQL regular expression may not contain more than two escape-double-quote separators"))); + nquotes++; + } + else + { + *r++ = UCharBackSlesh; + *r++ = pchar; + } + afterescape = false; + } + else if (e && elen > 0 && pchar == *e) + { + afterescape = true; + } + else if (incharclass) + { + if (pchar == UCharBackSlesh) + *r++ = UCharBackSlesh; + *r++ = pchar; + if (pchar == UCharRQBracket) + incharclass = false; + } + else if (pchar == UCharLQBracket) + { + *r++ = pchar; + incharclass = true; + } + else if (pchar == UCharPercent) + { + *r++ = UCharDot; + *r++ = UCharStar; + } + else if (pchar == UCharUnderLine) + *r++ = UCharDot; + else if (pchar == UCharLBracket) + { + *r++ = UCharLBracket; + *r++ = UCharQ; + *r++ = UCharDotDot; + } + else if (pchar == UCharBackSlesh || pchar == UCharDot || + pchar == UCharUp || pchar == UCharDollar) + { + *r++ = UCharBackSlesh; + *r++ = pchar; + } + else + *r++ = pchar; + + p++, plen--; + } + + *r++ = UCharRBracket; + *r++ = UCharDollar; + + return r-result; +} + +PG_FUNCTION_INFO_V1( mchar_similar_escape ); +Datum mchar_similar_escape( PG_FUNCTION_ARGS ); +Datum +mchar_similar_escape( PG_FUNCTION_ARGS ) { + MChar *pat; + MChar *esc; + MChar *result; + + if (PG_ARGISNULL(0)) + PG_RETURN_NULL(); + pat = PG_GETARG_MCHAR(0); + + if (PG_NARGS() < 2 || PG_ARGISNULL(1)) { + esc = NULL; + } else { + esc = PG_GETARG_MCHAR(1); + } + + result = (MChar*)palloc( MCHARHDRSZ + sizeof(UChar)*(23 + 3*UCHARLENGTH(pat)) ); + result->len = MCHARHDRSZ + do_similar_escape( pat->data, UCHARLENGTH(pat), + (esc) ? esc->data : NULL, (esc) ? UCHARLENGTH(esc) : -1, + result->data ) * sizeof(UChar); + result->typmod=-1; + + SET_VARSIZE(result, result->len); + PG_FREE_IF_COPY(pat,0); + if ( esc ) + PG_FREE_IF_COPY(esc,1); + + PG_RETURN_MCHAR(result); +} + +PG_FUNCTION_INFO_V1( mvarchar_similar_escape ); +Datum mvarchar_similar_escape( PG_FUNCTION_ARGS ); +Datum +mvarchar_similar_escape( PG_FUNCTION_ARGS ) { + MVarChar *pat; + MVarChar *esc; + MVarChar *result; + + if (PG_ARGISNULL(0)) + PG_RETURN_NULL(); + pat = PG_GETARG_MVARCHAR(0); + + if (PG_NARGS() < 2 || PG_ARGISNULL(1)) { + esc = NULL; + } else { + esc = PG_GETARG_MVARCHAR(1); + } + + result = (MVarChar*)palloc( MVARCHARHDRSZ + sizeof(UChar)*(23 + 3*UVARCHARLENGTH(pat)) ); + result->len = MVARCHARHDRSZ + do_similar_escape( pat->data, UVARCHARLENGTH(pat), + (esc) ? esc->data : NULL, (esc) ? UVARCHARLENGTH(esc) : -1, + result->data ) * sizeof(UChar); + + SET_VARSIZE(result, result->len); + PG_FREE_IF_COPY(pat,0); + if ( esc ) + PG_FREE_IF_COPY(esc,1); + + PG_RETURN_MVARCHAR(result); +} + +#define RE_CACHE_SIZE 32 diff --git a/contrib/mchar/mchar_op.c b/contrib/mchar/mchar_op.c new file mode 100644 index 00000000000..4694d9cf3c3 --- /dev/null +++ b/contrib/mchar/mchar_op.c @@ -0,0 +1,449 @@ +#include "mchar.h" + +int +lengthWithoutSpaceVarChar(MVarChar *m) { + int l = UVARCHARLENGTH(m); + + while( l>0 && m_isspace( m->data[ l-1 ] ) ) + l--; + + return l; +} + +int +lengthWithoutSpaceChar(MChar *m) { + int l = UCHARLENGTH(m); + + while( l>0 && m_isspace( m->data[ l-1 ] ) ) + l--; + + return l; +} + +static inline int +mchar_icase_compare( MChar *a, MChar *b ) { + return UCharCaseCompare( + a->data, lengthWithoutSpaceChar(a), + b->data, lengthWithoutSpaceChar(b) + ); +} + +static inline int +mchar_case_compare( MChar *a, MChar *b ) { + return UCharCompare( + a->data, lengthWithoutSpaceChar(a), + b->data, lengthWithoutSpaceChar(b) + ); +} + +#define MCHARCMPFUNC( c, type, action, ret ) \ +PG_FUNCTION_INFO_V1( mchar_##c##_##type ); \ +Datum mchar_##c##_##type(PG_FUNCTION_ARGS);\ +Datum \ +mchar_##c##_##type(PG_FUNCTION_ARGS) { \ + MChar *a = PG_GETARG_MCHAR(0); \ + MChar *b = PG_GETARG_MCHAR(1); \ + int res = mchar_##c##_compare(a,b); \ + \ + PG_FREE_IF_COPY(a,0); \ + PG_FREE_IF_COPY(b,1); \ + PG_RETURN_##ret( res action 0 ); \ +} + + +MCHARCMPFUNC( case, eq, ==, BOOL ) +MCHARCMPFUNC( case, ne, !=, BOOL ) +MCHARCMPFUNC( case, lt, <, BOOL ) +MCHARCMPFUNC( case, le, <=, BOOL ) +MCHARCMPFUNC( case, ge, >=, BOOL ) +MCHARCMPFUNC( case, gt, >, BOOL ) +MCHARCMPFUNC( case, cmp, +, INT32 ) + +MCHARCMPFUNC( icase, eq, ==, BOOL ) +MCHARCMPFUNC( icase, ne, !=, BOOL ) +MCHARCMPFUNC( icase, lt, <, BOOL ) +MCHARCMPFUNC( icase, le, <=, BOOL ) +MCHARCMPFUNC( icase, ge, >=, BOOL ) +MCHARCMPFUNC( icase, gt, >, BOOL ) +MCHARCMPFUNC( icase, cmp, +, INT32 ) + +PG_FUNCTION_INFO_V1( mchar_larger ); +Datum mchar_larger( PG_FUNCTION_ARGS ); +Datum +mchar_larger( PG_FUNCTION_ARGS ) { + MChar *a = PG_GETARG_MCHAR(0); + MChar *b = PG_GETARG_MCHAR(1); + MChar *r; + + r = ( mchar_icase_compare(a,b) > 0 ) ? a : b; + + PG_RETURN_MCHAR(r); +} + +PG_FUNCTION_INFO_V1( mchar_smaller ); +Datum mchar_smaller( PG_FUNCTION_ARGS ); +Datum +mchar_smaller( PG_FUNCTION_ARGS ) { + MChar *a = PG_GETARG_MCHAR(0); + MChar *b = PG_GETARG_MCHAR(1); + MChar *r; + + r = ( mchar_icase_compare(a,b) < 0 ) ? a : b; + + PG_RETURN_MCHAR(r); +} + + +PG_FUNCTION_INFO_V1( mchar_concat ); +Datum mchar_concat( PG_FUNCTION_ARGS ); +Datum +mchar_concat( PG_FUNCTION_ARGS ) { + MChar *a = PG_GETARG_MCHAR(0); + MChar *b = PG_GETARG_MCHAR(1); + MChar *result; + int maxcharlen, curlen; + int acharlen = u_countChar32(a->data, UCHARLENGTH(a)), + bcharlen = u_countChar32(b->data, UCHARLENGTH(b)); + + + maxcharlen = ((a->typmod<=0) ? acharlen : a->typmod) + + ((b->typmod<=0) ? bcharlen : b->typmod); + + result = (MChar*)palloc( MCHARHDRSZ + sizeof(UChar) * 2 * maxcharlen ); + + curlen = UCHARLENGTH( a ); + if ( curlen > 0 ) + memcpy( result->data, a->data, MCHARLENGTH(a) ); + if ( a->typmod > 0 && acharlen < a->typmod ) { + FillWhiteSpace( result->data + curlen, a->typmod-acharlen ); + curlen += a->typmod-acharlen; + } + + if ( UCHARLENGTH(b) > 0 ) { + memcpy( result->data + curlen, b->data, MCHARLENGTH( b ) ); + curlen += UCHARLENGTH( b ); + } + if ( b->typmod > 0 && bcharlen < b->typmod ) { + FillWhiteSpace( result->data + curlen, b->typmod-bcharlen ); + curlen += b->typmod-bcharlen; + } + + + result->typmod = -1; + SET_VARSIZE(result, sizeof(UChar) * curlen + MCHARHDRSZ); + + PG_FREE_IF_COPY(a,0); + PG_FREE_IF_COPY(b,1); + + PG_RETURN_MCHAR(result); +} + +static inline int +mvarchar_icase_compare( MVarChar *a, MVarChar *b ) { + + return UCharCaseCompare( + a->data, lengthWithoutSpaceVarChar(a), + b->data, lengthWithoutSpaceVarChar(b) + ); +} + +static inline int +mvarchar_case_compare( MVarChar *a, MVarChar *b ) { + return UCharCompare( + a->data, lengthWithoutSpaceVarChar(a), + b->data, lengthWithoutSpaceVarChar(b) + ); +} + +#define MVARCHARCMPFUNC( c, type, action, ret ) \ +PG_FUNCTION_INFO_V1( mvarchar_##c##_##type ); \ +Datum mvarchar_##c##_##type(PG_FUNCTION_ARGS); \ +Datum \ +mvarchar_##c##_##type(PG_FUNCTION_ARGS) { \ + MVarChar *a = PG_GETARG_MVARCHAR(0); \ + MVarChar *b = PG_GETARG_MVARCHAR(1); \ + int res = mvarchar_##c##_compare(a,b); \ + \ + PG_FREE_IF_COPY(a,0); \ + PG_FREE_IF_COPY(b,1); \ + PG_RETURN_##ret( res action 0 ); \ +} + + +MVARCHARCMPFUNC( case, eq, ==, BOOL ) +MVARCHARCMPFUNC( case, ne, !=, BOOL ) +MVARCHARCMPFUNC( case, lt, <, BOOL ) +MVARCHARCMPFUNC( case, le, <=, BOOL ) +MVARCHARCMPFUNC( case, ge, >=, BOOL ) +MVARCHARCMPFUNC( case, gt, >, BOOL ) +MVARCHARCMPFUNC( case, cmp, +, INT32 ) + +MVARCHARCMPFUNC( icase, eq, ==, BOOL ) +MVARCHARCMPFUNC( icase, ne, !=, BOOL ) +MVARCHARCMPFUNC( icase, lt, <, BOOL ) +MVARCHARCMPFUNC( icase, le, <=, BOOL ) +MVARCHARCMPFUNC( icase, ge, >=, BOOL ) +MVARCHARCMPFUNC( icase, gt, >, BOOL ) +MVARCHARCMPFUNC( icase, cmp, +, INT32 ) + +PG_FUNCTION_INFO_V1( mvarchar_larger ); +Datum mvarchar_larger( PG_FUNCTION_ARGS ); +Datum +mvarchar_larger( PG_FUNCTION_ARGS ) { + MVarChar *a = PG_GETARG_MVARCHAR(0); + MVarChar *b = PG_GETARG_MVARCHAR(1); + MVarChar *r; + + r = ( mvarchar_icase_compare(a,b) > 0 ) ? a : b; + + PG_RETURN_MVARCHAR(r); +} + +PG_FUNCTION_INFO_V1( mvarchar_smaller ); +Datum mvarchar_smaller( PG_FUNCTION_ARGS ); +Datum +mvarchar_smaller( PG_FUNCTION_ARGS ) { + MVarChar *a = PG_GETARG_MVARCHAR(0); + MVarChar *b = PG_GETARG_MVARCHAR(1); + MVarChar *r; + + r = ( mvarchar_icase_compare(a,b) < 0 ) ? a : b; + + PG_RETURN_MVARCHAR(r); +} + +PG_FUNCTION_INFO_V1( mvarchar_concat ); +Datum mvarchar_concat( PG_FUNCTION_ARGS ); +Datum +mvarchar_concat( PG_FUNCTION_ARGS ) { + MVarChar *a = PG_GETARG_MVARCHAR(0); + MVarChar *b = PG_GETARG_MVARCHAR(1); + MVarChar *result; + int curlen; + int acharlen = u_countChar32(a->data, UVARCHARLENGTH(a)), + bcharlen = u_countChar32(b->data, UVARCHARLENGTH(b)); + + result = (MVarChar*)palloc( MVARCHARHDRSZ + sizeof(UChar) * 2 * (acharlen + bcharlen) ); + + curlen = UVARCHARLENGTH( a ); + if ( curlen > 0 ) + memcpy( result->data, a->data, MVARCHARLENGTH(a) ); + + if ( UVARCHARLENGTH(b) > 0 ) { + memcpy( result->data + curlen, b->data, MVARCHARLENGTH( b ) ); + curlen += UVARCHARLENGTH( b ); + } + + SET_VARSIZE(result, sizeof(UChar) * curlen + MVARCHARHDRSZ); + + PG_FREE_IF_COPY(a,0); + PG_FREE_IF_COPY(b,1); + + PG_RETURN_MVARCHAR(result); +} + +PG_FUNCTION_INFO_V1( mchar_mvarchar_concat ); +Datum mchar_mvarchar_concat( PG_FUNCTION_ARGS ); +Datum +mchar_mvarchar_concat( PG_FUNCTION_ARGS ) { + MChar *a = PG_GETARG_MCHAR(0); + MVarChar *b = PG_GETARG_MVARCHAR(1); + MVarChar *result; + int curlen, maxcharlen; + int acharlen = u_countChar32(a->data, UCHARLENGTH(a)), + bcharlen = u_countChar32(b->data, UVARCHARLENGTH(b)); + + maxcharlen = ((a->typmod<=0) ? acharlen : a->typmod) + bcharlen; + + result = (MVarChar*)palloc( MVARCHARHDRSZ + sizeof(UChar) * 2 * maxcharlen ); + + curlen = UCHARLENGTH( a ); + if ( curlen > 0 ) + memcpy( result->data, a->data, MCHARLENGTH(a) ); + if ( a->typmod > 0 && acharlen < a->typmod ) { + FillWhiteSpace( result->data + curlen, a->typmod-acharlen ); + curlen += a->typmod-acharlen; + } + + if ( UVARCHARLENGTH(b) > 0 ) { + memcpy( result->data + curlen, b->data, MVARCHARLENGTH( b ) ); + curlen += UVARCHARLENGTH( b ); + } + + SET_VARSIZE(result, sizeof(UChar) * curlen + MVARCHARHDRSZ); + + PG_FREE_IF_COPY(a,0); + PG_FREE_IF_COPY(b,1); + + PG_RETURN_MVARCHAR(result); +} + +PG_FUNCTION_INFO_V1( mvarchar_mchar_concat ); +Datum mvarchar_mchar_concat( PG_FUNCTION_ARGS ); +Datum +mvarchar_mchar_concat( PG_FUNCTION_ARGS ) { + MVarChar *a = PG_GETARG_MVARCHAR(0); + MChar *b = PG_GETARG_MCHAR(1); + MVarChar *result; + int curlen, maxcharlen; + int acharlen = u_countChar32(a->data, UVARCHARLENGTH(a)), + bcharlen = u_countChar32(b->data, UCHARLENGTH(b)); + + maxcharlen = acharlen + ((b->typmod<=0) ? bcharlen : b->typmod); + + result = (MVarChar*)palloc( MVARCHARHDRSZ + sizeof(UChar) * 2 * maxcharlen ); + + curlen = UVARCHARLENGTH( a ); + if ( curlen > 0 ) + memcpy( result->data, a->data, MVARCHARLENGTH(a) ); + + if ( UCHARLENGTH(b) > 0 ) { + memcpy( result->data + curlen, b->data, MCHARLENGTH( b ) ); + curlen += UCHARLENGTH( b ); + } + if ( b->typmod > 0 && bcharlen < b->typmod ) { + FillWhiteSpace( result->data + curlen, b->typmod-bcharlen ); + curlen += b->typmod-bcharlen; + } + + SET_VARSIZE(result, sizeof(UChar) * curlen + MVARCHARHDRSZ); + + PG_FREE_IF_COPY(a,0); + PG_FREE_IF_COPY(b,1); + + PG_RETURN_MVARCHAR(result); +} + +/* + * mchar <> mvarchar + */ +static inline int +mc_mv_icase_compare( MChar *a, MVarChar *b ) { + return UCharCaseCompare( + a->data, lengthWithoutSpaceChar(a), + b->data, lengthWithoutSpaceVarChar(b) + ); +} + +static inline int +mc_mv_case_compare( MChar *a, MVarChar *b ) { + return UCharCompare( + a->data, lengthWithoutSpaceChar(a), + b->data, lengthWithoutSpaceVarChar(b) + ); +} + +#define MC_MV_CHARCMPFUNC( c, type, action, ret ) \ +PG_FUNCTION_INFO_V1( mc_mv_##c##_##type ); \ +Datum mc_mv_##c##_##type(PG_FUNCTION_ARGS);\ +Datum \ +mc_mv_##c##_##type(PG_FUNCTION_ARGS) { \ + MChar *a = PG_GETARG_MCHAR(0); \ + MVarChar *b = PG_GETARG_MVARCHAR(1); \ + int res = mc_mv_##c##_compare(a,b); \ + \ + PG_FREE_IF_COPY(a,0); \ + PG_FREE_IF_COPY(b,1); \ + PG_RETURN_##ret( res action 0 ); \ +} + + +MC_MV_CHARCMPFUNC( case, eq, ==, BOOL ) +MC_MV_CHARCMPFUNC( case, ne, !=, BOOL ) +MC_MV_CHARCMPFUNC( case, lt, <, BOOL ) +MC_MV_CHARCMPFUNC( case, le, <=, BOOL ) +MC_MV_CHARCMPFUNC( case, ge, >=, BOOL ) +MC_MV_CHARCMPFUNC( case, gt, >, BOOL ) +MC_MV_CHARCMPFUNC( case, cmp, +, INT32 ) + +MC_MV_CHARCMPFUNC( icase, eq, ==, BOOL ) +MC_MV_CHARCMPFUNC( icase, ne, !=, BOOL ) +MC_MV_CHARCMPFUNC( icase, lt, <, BOOL ) +MC_MV_CHARCMPFUNC( icase, le, <=, BOOL ) +MC_MV_CHARCMPFUNC( icase, ge, >=, BOOL ) +MC_MV_CHARCMPFUNC( icase, gt, >, BOOL ) +MC_MV_CHARCMPFUNC( icase, cmp, +, INT32 ) + +/* + * mvarchar <> mchar + */ +static inline int +mv_mc_icase_compare( MVarChar *a, MChar *b ) { + return UCharCaseCompare( + a->data, lengthWithoutSpaceVarChar(a), + b->data, lengthWithoutSpaceChar(b) + ); +} + +static inline int +mv_mc_case_compare( MVarChar *a, MChar *b ) { + return UCharCompare( + a->data, lengthWithoutSpaceVarChar(a), + b->data, lengthWithoutSpaceChar(b) + ); +} + +#define MV_MC_CHARCMPFUNC( c, type, action, ret ) \ +PG_FUNCTION_INFO_V1( mv_mc_##c##_##type ); \ +Datum mv_mc_##c##_##type(PG_FUNCTION_ARGS);\ +Datum \ +mv_mc_##c##_##type(PG_FUNCTION_ARGS) { \ + MVarChar *a = PG_GETARG_MVARCHAR(0); \ + MChar *b = PG_GETARG_MCHAR(1); \ + int res = mv_mc_##c##_compare(a,b); \ + \ + PG_FREE_IF_COPY(a,0); \ + PG_FREE_IF_COPY(b,1); \ + PG_RETURN_##ret( res action 0 ); \ +} + + +MV_MC_CHARCMPFUNC( case, eq, ==, BOOL ) +MV_MC_CHARCMPFUNC( case, ne, !=, BOOL ) +MV_MC_CHARCMPFUNC( case, lt, <, BOOL ) +MV_MC_CHARCMPFUNC( case, le, <=, BOOL ) +MV_MC_CHARCMPFUNC( case, ge, >=, BOOL ) +MV_MC_CHARCMPFUNC( case, gt, >, BOOL ) +MV_MC_CHARCMPFUNC( case, cmp, +, INT32 ) + +MV_MC_CHARCMPFUNC( icase, eq, ==, BOOL ) +MV_MC_CHARCMPFUNC( icase, ne, !=, BOOL ) +MV_MC_CHARCMPFUNC( icase, lt, <, BOOL ) +MV_MC_CHARCMPFUNC( icase, le, <=, BOOL ) +MV_MC_CHARCMPFUNC( icase, ge, >=, BOOL ) +MV_MC_CHARCMPFUNC( icase, gt, >, BOOL ) +MV_MC_CHARCMPFUNC( icase, cmp, +, INT32 ) + +#define NULLHASHVALUE (-2147483647) + +#define FULLEQ_FUNC(type, cmpfunc, hashfunc) \ +PG_FUNCTION_INFO_V1( isfulleq_##type ); \ +Datum isfulleq_##type(PG_FUNCTION_ARGS); \ +Datum \ +isfulleq_##type(PG_FUNCTION_ARGS) { \ + if ( PG_ARGISNULL(0) && PG_ARGISNULL(1) ) \ + PG_RETURN_BOOL(true); \ + else if ( PG_ARGISNULL(0) || PG_ARGISNULL(1) ) \ + PG_RETURN_BOOL(false); \ + \ + PG_RETURN_DATUM( DirectFunctionCall2( cmpfunc, \ + PG_GETARG_DATUM(0), \ + PG_GETARG_DATUM(1) \ + ) ); \ +} \ + \ +PG_FUNCTION_INFO_V1( fullhash_##type ); \ +Datum fullhash_##type(PG_FUNCTION_ARGS); \ +Datum \ +fullhash_##type(PG_FUNCTION_ARGS) { \ + if ( PG_ARGISNULL(0) ) \ + PG_RETURN_INT32(NULLHASHVALUE); \ + \ + PG_RETURN_DATUM( DirectFunctionCall1( hashfunc, \ + PG_GETARG_DATUM(0) \ + ) ); \ +} + +FULLEQ_FUNC( mchar, mchar_icase_eq, mchar_hash ); +FULLEQ_FUNC( mvarchar, mvarchar_icase_eq, mvarchar_hash ); + diff --git a/contrib/mchar/mchar_proc.c b/contrib/mchar/mchar_proc.c new file mode 100644 index 00000000000..edabfb5eb66 --- /dev/null +++ b/contrib/mchar/mchar_proc.c @@ -0,0 +1,315 @@ +#include "mchar.h" +#include "mb/pg_wchar.h" + +PG_FUNCTION_INFO_V1(mchar_length); +Datum mchar_length(PG_FUNCTION_ARGS); + +Datum +mchar_length(PG_FUNCTION_ARGS) { + MChar *m = PG_GETARG_MCHAR(0); + int32 l = UCHARLENGTH(m); + + while( l>0 && m_isspace( m->data[ l-1 ] ) ) + l--; + + l = u_countChar32(m->data, l); + + PG_FREE_IF_COPY(m,0); + + PG_RETURN_INT32(l); +} + +PG_FUNCTION_INFO_V1(mvarchar_length); +Datum mvarchar_length(PG_FUNCTION_ARGS); + +Datum +mvarchar_length(PG_FUNCTION_ARGS) { + MVarChar *m = PG_GETARG_MVARCHAR(0); + int32 l = UVARCHARLENGTH(m); + + while( l>0 && m_isspace( m->data[ l-1 ] ) ) + l--; + + l = u_countChar32(m->data, l); + + PG_FREE_IF_COPY(m,0); + + PG_RETURN_INT32(l); +} + +static int32 +uchar_substring( + UChar *str, int32 strl, + int32 start, int32 length, bool length_not_specified, + UChar *dst) { + int32 S = start-1; /* start position */ + int32 S1; /* adjusted start position */ + int32 L1; /* adjusted substring length */ + int32 subbegin=0, subend=0; + + S1 = Max(S, 0); + if (length_not_specified) + L1 = -1; + else { + /* end position */ + int32 E = S + length; + + /* + * A negative value for L is the only way for the end position to + * be before the start. SQL99 says to throw an error. + */ + + if (E < S) + ereport(ERROR, + (errcode(ERRCODE_SUBSTRING_ERROR), + errmsg("negative substring length not allowed"))); + + /* + * A zero or negative value for the end position can happen if the + * start was negative or one. SQL99 says to return a zero-length + * string. + */ + if (E < 0) + return 0; + + L1 = E - S1; + } + + U16_FWD_N( str, subbegin, strl, S1 ); + if ( subbegin >= strl ) + return 0; + subend = subbegin; + U16_FWD_N( str, subend, strl, L1 ); + + memcpy( dst, str+subbegin, sizeof(UChar)*(subend-subbegin) ); + + return subend-subbegin; +} + +PG_FUNCTION_INFO_V1(mchar_substring); +Datum mchar_substring(PG_FUNCTION_ARGS); +Datum +mchar_substring(PG_FUNCTION_ARGS) { + MChar *src = PG_GETARG_MCHAR(0); + MChar *dst; + int32 length; + + dst = (MChar*)palloc( VARSIZE(src) ); + length = uchar_substring( + src->data, UCHARLENGTH(src), + PG_GETARG_INT32(1), PG_GETARG_INT32(2), false, + dst->data); + + dst->typmod = src->typmod; + SET_VARSIZE(dst, MCHARHDRSZ + length *sizeof(UChar)); + + PG_FREE_IF_COPY(src, 0); + PG_RETURN_MCHAR(dst); +} + +PG_FUNCTION_INFO_V1(mchar_substring_no_len); +Datum mchar_substring_no_len(PG_FUNCTION_ARGS); +Datum +mchar_substring_no_len(PG_FUNCTION_ARGS) { + MChar *src = PG_GETARG_MCHAR(0); + MChar *dst; + int32 length; + + dst = (MChar*)palloc( VARSIZE(src) ); + length = uchar_substring( + src->data, UCHARLENGTH(src), + PG_GETARG_INT32(1), -1, true, + dst->data); + + dst->typmod = src->typmod; + SET_VARSIZE(dst, MCHARHDRSZ + length *sizeof(UChar)); + + PG_FREE_IF_COPY(src, 0); + PG_RETURN_MCHAR(dst); +} + +PG_FUNCTION_INFO_V1(mvarchar_substring); +Datum mvarchar_substring(PG_FUNCTION_ARGS); +Datum +mvarchar_substring(PG_FUNCTION_ARGS) { + MVarChar *src = PG_GETARG_MVARCHAR(0); + MVarChar *dst; + int32 length; + + dst = (MVarChar*)palloc( VARSIZE(src) ); + length = uchar_substring( + src->data, UVARCHARLENGTH(src), + PG_GETARG_INT32(1), PG_GETARG_INT32(2), false, + dst->data); + + SET_VARSIZE(dst, MVARCHARHDRSZ + length *sizeof(UChar)); + + PG_FREE_IF_COPY(src, 0); + PG_RETURN_MVARCHAR(dst); +} + +PG_FUNCTION_INFO_V1(mvarchar_substring_no_len); +Datum mvarchar_substring_no_len(PG_FUNCTION_ARGS); +Datum +mvarchar_substring_no_len(PG_FUNCTION_ARGS) { + MVarChar *src = PG_GETARG_MVARCHAR(0); + MVarChar *dst; + int32 length; + + dst = (MVarChar*)palloc( VARSIZE(src) ); + length = uchar_substring( + src->data, UVARCHARLENGTH(src), + PG_GETARG_INT32(1), -1, true, + dst->data); + + SET_VARSIZE(dst, MVARCHARHDRSZ + length *sizeof(UChar)); + + PG_FREE_IF_COPY(src, 0); + PG_RETURN_MVARCHAR(dst); +} + +PG_FUNCTION_INFO_V1(mvarchar_hash); +Datum +mvarchar_hash(PG_FUNCTION_ARGS) { + MVarChar *src = PG_GETARG_MVARCHAR(0); + Datum res; + + res = hash_uchar( src->data, lengthWithoutSpaceVarChar(src) ); + + PG_FREE_IF_COPY(src, 0); + PG_RETURN_DATUM( res ); +} + +PG_FUNCTION_INFO_V1(mchar_hash); +Datum +mchar_hash(PG_FUNCTION_ARGS) { + MChar *src = PG_GETARG_MCHAR(0); + Datum res; + + res = hash_uchar( src->data, lengthWithoutSpaceChar(src) ); + + PG_FREE_IF_COPY(src, 0); + PG_RETURN_DATUM( res ); +} + +PG_FUNCTION_INFO_V1(mchar_upper); +Datum mchar_upper(PG_FUNCTION_ARGS); +Datum +mchar_upper(PG_FUNCTION_ARGS) { + MChar *src = PG_GETARG_MCHAR(0); + MChar *dst = (MChar*)palloc( VARSIZE(src) * 2 ); + + dst->len = MCHARHDRSZ; + dst->typmod = src->typmod; + if ( UCHARLENGTH(src) != 0 ) { + int length; + UErrorCode err=0; + + length = u_strToUpper( dst->data, VARSIZE(src) * 2 - MCHARHDRSZ, + src->data, UCHARLENGTH(src), + NULL, &err ); + + Assert( length <= VARSIZE(src) * 2 - MCHARHDRSZ ); + + if ( U_FAILURE(err) ) + elog(ERROR,"ICU u_strToUpper fails and returns %d (%s)", err, u_errorName(err)); + + dst->len += sizeof(UChar) * length; + } + + SET_VARSIZE( dst, dst->len ); + PG_FREE_IF_COPY(src, 0); + PG_RETURN_MCHAR( dst ); +} + +PG_FUNCTION_INFO_V1(mchar_lower); +Datum mchar_lower(PG_FUNCTION_ARGS); +Datum +mchar_lower(PG_FUNCTION_ARGS) { + MChar *src = PG_GETARG_MCHAR(0); + MChar *dst = (MChar*)palloc( VARSIZE(src) * 2 ); + + dst->len = MCHARHDRSZ; + dst->typmod = src->typmod; + if ( UCHARLENGTH(src) != 0 ) { + int length; + UErrorCode err=0; + + length = u_strToLower( dst->data, VARSIZE(src) * 2 - MCHARHDRSZ, + src->data, UCHARLENGTH(src), + NULL, &err ); + + Assert( length <= VARSIZE(src) * 2 - MCHARHDRSZ ); + + if ( U_FAILURE(err) ) + elog(ERROR,"ICU u_strToLower fails and returns %d (%s)", err, u_errorName(err)); + + dst->len += sizeof(UChar) * length; + } + + SET_VARSIZE( dst, dst->len ); + PG_FREE_IF_COPY(src, 0); + PG_RETURN_MCHAR( dst ); +} + +PG_FUNCTION_INFO_V1(mvarchar_upper); +Datum mvarchar_upper(PG_FUNCTION_ARGS); +Datum +mvarchar_upper(PG_FUNCTION_ARGS) { + MVarChar *src = PG_GETARG_MVARCHAR(0); + MVarChar *dst = (MVarChar*)palloc( VARSIZE(src) * 2 ); + + dst->len = MVARCHARHDRSZ; + + if ( UVARCHARLENGTH(src) != 0 ) { + int length; + UErrorCode err=0; + + length = u_strToUpper( dst->data, VARSIZE(src) * 2 - MVARCHARHDRSZ, + src->data, UVARCHARLENGTH(src), + NULL, &err ); + + Assert( length <= VARSIZE(src) * 2 - MVARCHARHDRSZ ); + + if ( U_FAILURE(err) ) + elog(ERROR,"ICU u_strToUpper fails and returns %d (%s)", err, u_errorName(err)); + + dst->len += sizeof(UChar) * length; + } + + SET_VARSIZE( dst, dst->len ); + PG_FREE_IF_COPY(src, 0); + PG_RETURN_MVARCHAR( dst ); +} + +PG_FUNCTION_INFO_V1(mvarchar_lower); +Datum mvarchar_lower(PG_FUNCTION_ARGS); +Datum +mvarchar_lower(PG_FUNCTION_ARGS) { + MVarChar *src = PG_GETARG_MVARCHAR(0); + MVarChar *dst = (MVarChar*)palloc( VARSIZE(src) * 2 ); + + dst->len = MVARCHARHDRSZ; + + if ( UVARCHARLENGTH(src) != 0 ) { + int length; + UErrorCode err=0; + + length = u_strToLower( dst->data, VARSIZE(src) * 2 - MVARCHARHDRSZ, + src->data, UVARCHARLENGTH(src), + NULL, &err ); + + Assert( length <= VARSIZE(src) * 2 - MVARCHARHDRSZ ); + + if ( U_FAILURE(err) ) + elog(ERROR,"ICU u_strToLower fails and returns %d (%s)", err, u_errorName(err)); + + dst->len += sizeof(UChar) * length; + } + + SET_VARSIZE( dst, dst->len ); + PG_FREE_IF_COPY(src, 0); + PG_RETURN_MVARCHAR( dst ); +} + + diff --git a/contrib/mchar/mchar_recode.c b/contrib/mchar/mchar_recode.c new file mode 100644 index 00000000000..12bc6d4f3aa --- /dev/null +++ b/contrib/mchar/mchar_recode.c @@ -0,0 +1,166 @@ +#include "mchar.h" +#include "access/hash.h" + +#include "unicode/ucol.h" +#include "unicode/ucnv.h" + +static UConverter *cnvDB = NULL; +static UCollator *colCaseInsensitive = NULL; +static UCollator *colCaseSensitive = NULL; + +static void +createUObjs() { + if ( !cnvDB ) { + UErrorCode err = 0; + + if ( GetDatabaseEncoding() == PG_UTF8 ) + cnvDB = ucnv_open("UTF8", &err); + else + cnvDB = ucnv_open(NULL, &err); + if ( U_FAILURE(err) || cnvDB == NULL ) + elog(ERROR,"ICU ucnv_open returns %d (%s)", err, u_errorName(err)); + } + + if ( !colCaseInsensitive ) { + UErrorCode err = 0; + + colCaseInsensitive = ucol_open("", &err); + if ( U_FAILURE(err) || cnvDB == NULL ) { + if ( colCaseSensitive ) + ucol_close( colCaseSensitive ); + colCaseSensitive = NULL; + elog(ERROR,"ICU ucol_open returns %d (%s)", err, u_errorName(err)); + } + + ucol_setStrength( colCaseInsensitive, UCOL_SECONDARY ); + } + + if ( !colCaseSensitive ) { + UErrorCode err = 0; + + colCaseSensitive = ucol_open("", &err); + if ( U_FAILURE(err) || cnvDB == NULL ) { + if ( colCaseSensitive ) + ucol_close( colCaseSensitive ); + colCaseSensitive = NULL; + elog(ERROR,"ICU ucol_open returns %d (%s)", err, u_errorName(err)); + } + + ucol_setAttribute(colCaseSensitive, UCOL_CASE_FIRST, UCOL_UPPER_FIRST, &err); + if (U_FAILURE(err)) { + if ( colCaseSensitive ) + ucol_close( colCaseSensitive ); + colCaseSensitive = NULL; + elog(ERROR,"ICU ucol_setAttribute returns %d (%s)", err, u_errorName(err)); + } + } +} + +int +Char2UChar(const char * src, int srclen, UChar *dst) { + int dstlen=0; + UErrorCode err = 0; + + createUObjs(); + dstlen = ucnv_toUChars( cnvDB, dst, srclen*4, src, srclen, &err ); + if ( U_FAILURE(err)) + elog(ERROR,"ICU ucnv_toUChars returns %d (%s)", err, u_errorName(err)); + + return dstlen; +} + +int +UChar2Char(const UChar * src, int srclen, char *dst) { + int dstlen=0; + UErrorCode err = 0; + + createUObjs(); + dstlen = ucnv_fromUChars( cnvDB, dst, srclen*4, src, srclen, &err ); + if ( U_FAILURE(err) ) + elog(ERROR,"ICU ucnv_fromUChars returns %d (%s)", err, u_errorName(err)); + + return dstlen; +} + +int +UChar2Wchar(UChar * src, int srclen, pg_wchar *dst) { + int dstlen=0; + char *utf = palloc(sizeof(char)*srclen*4); + + dstlen = UChar2Char(src, srclen, utf); + dstlen = pg_mb2wchar_with_len( utf, dst, dstlen ); + pfree(utf); + + return dstlen; +} + +static UChar UCharWhiteSpace = 0; + +void +FillWhiteSpace( UChar *dst, int n ) { + if ( UCharWhiteSpace == 0 ) { + int len; + UErrorCode err = 0; + + u_strFromUTF8( &UCharWhiteSpace, 1, &len, " ", 1, &err); + + Assert( len==1 ); + Assert( !U_FAILURE(err) ); + } + + while( n-- > 0 ) + *dst++ = UCharWhiteSpace; +} + +int +UCharCaseCompare(UChar * a, int alen, UChar *b, int blen) { + + createUObjs(); + + return (int)ucol_strcoll( colCaseInsensitive, + a, alen, + b, blen); +} + +int +UCharCompare(UChar * a, int alen, UChar *b, int blen) { + + createUObjs(); + + return (int)ucol_strcoll( colCaseSensitive, + a, alen, + b, blen); +} + +Datum +hash_uchar( UChar *s, int len ) { + int32 length = INT_MAX, i; + Datum res; + uint8 *d; + + if ( len == 0 ) + return hash_any( NULL, 0 ); + + createUObjs(); + + for(i=2;; i*=2) + { + d = palloc(len * i); + length = ucol_getSortKey(colCaseInsensitive, s, len, d, len*i); + + if (length == 0) + elog(ERROR,"ICU ucol_getSortKey fails"); + + if (length < len*i) + break; + + pfree(d); + } + + res = hash_any( (unsigned char*) d, length); + + pfree(d); + + return res; +} + diff --git a/contrib/mchar/sql/compat.sql b/contrib/mchar/sql/compat.sql new file mode 100644 index 00000000000..d5b6a986960 --- /dev/null +++ b/contrib/mchar/sql/compat.sql @@ -0,0 +1,11 @@ +--- table based checks + +select '<' || ch || '>', '<' || vch || '>' from chvch; +select * from chvch where vch = 'One space'; +select * from chvch where vch = 'One space '; + +select * from ch where chcol = 'abcd' order by chcol; +select * from ch t1 join ch t2 on t1.chcol = t2.chcol order by t1.chcol, t2.chcol; +select * from ch where chcol > 'abcd' and chcol<'ee'; +select * from ch order by chcol; + diff --git a/contrib/mchar/sql/init.sql b/contrib/mchar/sql/init.sql new file mode 100644 index 00000000000..04310044458 --- /dev/null +++ b/contrib/mchar/sql/init.sql @@ -0,0 +1,23 @@ +CREATE EXTENSION mchar; + +create table ch ( + chcol mchar(32) +) without oids; + +insert into ch values('abcd'); +insert into ch values('AbcD'); +insert into ch values('abcz'); +insert into ch values('defg'); +insert into ch values('dEfg'); +insert into ch values('ee'); +insert into ch values('Ee'); + +create table chvch ( + ch mchar(12), + vch mvarchar(12) +) without oids; + +insert into chvch values('No spaces', 'No spaces'); +insert into chvch values('One space ', 'One space '); +insert into chvch values('1 space', '1 space '); + diff --git a/contrib/mchar/sql/like.sql b/contrib/mchar/sql/like.sql new file mode 100644 index 00000000000..c29cf4eb6f9 --- /dev/null +++ b/contrib/mchar/sql/like.sql @@ -0,0 +1,231 @@ +-- simplest examples +-- E061-04 like predicate +set standard_conforming_strings=off; + +SELECT 'hawkeye'::mchar LIKE 'h%' AS "true"; +SELECT 'hawkeye'::mchar NOT LIKE 'h%' AS "false"; + +SELECT 'hawkeye'::mchar LIKE 'H%' AS "true"; +SELECT 'hawkeye'::mchar NOT LIKE 'H%' AS "false"; + +SELECT 'hawkeye'::mchar LIKE 'indio%' AS "false"; +SELECT 'hawkeye'::mchar NOT LIKE 'indio%' AS "true"; + +SELECT 'hawkeye'::mchar LIKE 'h%eye' AS "true"; +SELECT 'hawkeye'::mchar NOT LIKE 'h%eye' AS "false"; + +SELECT 'indio'::mchar LIKE '_ndio' AS "true"; +SELECT 'indio'::mchar NOT LIKE '_ndio' AS "false"; + +SELECT 'indio'::mchar LIKE 'in__o' AS "true"; +SELECT 'indio'::mchar NOT LIKE 'in__o' AS "false"; + +SELECT 'indio'::mchar LIKE 'in_o' AS "false"; +SELECT 'indio'::mchar NOT LIKE 'in_o' AS "true"; + +SELECT 'hawkeye'::mvarchar LIKE 'h%' AS "true"; +SELECT 'hawkeye'::mvarchar NOT LIKE 'h%' AS "false"; + +SELECT 'hawkeye'::mvarchar LIKE 'H%' AS "true"; +SELECT 'hawkeye'::mvarchar NOT LIKE 'H%' AS "false"; + +SELECT 'hawkeye'::mvarchar LIKE 'indio%' AS "false"; +SELECT 'hawkeye'::mvarchar NOT LIKE 'indio%' AS "true"; + +SELECT 'hawkeye'::mvarchar LIKE 'h%eye' AS "true"; +SELECT 'hawkeye'::mvarchar NOT LIKE 'h%eye' AS "false"; + +SELECT 'indio'::mvarchar LIKE '_ndio' AS "true"; +SELECT 'indio'::mvarchar NOT LIKE '_ndio' AS "false"; + +SELECT 'indio'::mvarchar LIKE 'in__o' AS "true"; +SELECT 'indio'::mvarchar NOT LIKE 'in__o' AS "false"; + +SELECT 'indio'::mvarchar LIKE 'in_o' AS "false"; +SELECT 'indio'::mvarchar NOT LIKE 'in_o' AS "true"; + +-- unused escape character +SELECT 'hawkeye'::mchar LIKE 'h%'::mchar ESCAPE '#' AS "true"; +SELECT 'hawkeye'::mchar NOT LIKE 'h%'::mchar ESCAPE '#' AS "false"; + +SELECT 'indio'::mchar LIKE 'ind_o'::mchar ESCAPE '$' AS "true"; +SELECT 'indio'::mchar NOT LIKE 'ind_o'::mchar ESCAPE '$' AS "false"; + +-- escape character +-- E061-05 like predicate with escape clause +SELECT 'h%'::mchar LIKE 'h#%'::mchar ESCAPE '#' AS "true"; +SELECT 'h%'::mchar NOT LIKE 'h#%'::mchar ESCAPE '#' AS "false"; + +SELECT 'h%wkeye'::mchar LIKE 'h#%'::mchar ESCAPE '#' AS "false"; +SELECT 'h%wkeye'::mchar NOT LIKE 'h#%'::mchar ESCAPE '#' AS "true"; + +SELECT 'h%wkeye'::mchar LIKE 'h#%%'::mchar ESCAPE '#' AS "true"; +SELECT 'h%wkeye'::mchar NOT LIKE 'h#%%'::mchar ESCAPE '#' AS "false"; + +SELECT 'h%awkeye'::mchar LIKE 'h#%a%k%e'::mchar ESCAPE '#' AS "true"; +SELECT 'h%awkeye'::mchar NOT LIKE 'h#%a%k%e'::mchar ESCAPE '#' AS "false"; + +SELECT 'indio'::mchar LIKE '_ndio'::mchar ESCAPE '$' AS "true"; +SELECT 'indio'::mchar NOT LIKE '_ndio'::mchar ESCAPE '$' AS "false"; + +SELECT 'i_dio'::mchar LIKE 'i$_d_o'::mchar ESCAPE '$' AS "true"; +SELECT 'i_dio'::mchar NOT LIKE 'i$_d_o'::mchar ESCAPE '$' AS "false"; + +SELECT 'i_dio'::mchar LIKE 'i$_nd_o'::mchar ESCAPE '$' AS "false"; +SELECT 'i_dio'::mchar NOT LIKE 'i$_nd_o'::mchar ESCAPE '$' AS "true"; + +SELECT 'i_dio'::mchar LIKE 'i$_d%o'::mchar ESCAPE '$' AS "true"; +SELECT 'i_dio'::mchar NOT LIKE 'i$_d%o'::mchar ESCAPE '$' AS "false"; + +-- escape character same as pattern character +SELECT 'maca'::mchar LIKE 'm%aca' ESCAPE '%'::mchar AS "true"; +SELECT 'maca'::mchar NOT LIKE 'm%aca' ESCAPE '%'::mchar AS "false"; + +SELECT 'ma%a'::mchar LIKE 'm%a%%a' ESCAPE '%'::mchar AS "true"; +SELECT 'ma%a'::mchar NOT LIKE 'm%a%%a' ESCAPE '%'::mchar AS "false"; + +SELECT 'bear'::mchar LIKE 'b_ear' ESCAPE '_'::mchar AS "true"; +SELECT 'bear'::mchar NOT LIKE 'b_ear'::mchar ESCAPE '_' AS "false"; + +SELECT 'be_r'::mchar LIKE 'b_e__r' ESCAPE '_'::mchar AS "true"; +SELECT 'be_r'::mchar NOT LIKE 'b_e__r' ESCAPE '_'::mchar AS "false"; + +SELECT 'be_r'::mchar LIKE '__e__r' ESCAPE '_'::mchar AS "false"; +SELECT 'be_r'::mchar NOT LIKE '__e__r'::mchar ESCAPE '_' AS "true"; + +-- unused escape character +SELECT 'hawkeye'::mvarchar LIKE 'h%'::mvarchar ESCAPE '#' AS "true"; +SELECT 'hawkeye'::mvarchar NOT LIKE 'h%'::mvarchar ESCAPE '#' AS "false"; + +SELECT 'indio'::mvarchar LIKE 'ind_o'::mvarchar ESCAPE '$' AS "true"; +SELECT 'indio'::mvarchar NOT LIKE 'ind_o'::mvarchar ESCAPE '$' AS "false"; + +-- escape character +-- E061-05 like predicate with escape clause +SELECT 'h%'::mvarchar LIKE 'h#%'::mvarchar ESCAPE '#' AS "true"; +SELECT 'h%'::mvarchar NOT LIKE 'h#%'::mvarchar ESCAPE '#' AS "false"; + +SELECT 'h%wkeye'::mvarchar LIKE 'h#%'::mvarchar ESCAPE '#' AS "false"; +SELECT 'h%wkeye'::mvarchar NOT LIKE 'h#%'::mvarchar ESCAPE '#' AS "true"; + +SELECT 'h%wkeye'::mvarchar LIKE 'h#%%'::mvarchar ESCAPE '#' AS "true"; +SELECT 'h%wkeye'::mvarchar NOT LIKE 'h#%%'::mvarchar ESCAPE '#' AS "false"; + +SELECT 'h%awkeye'::mvarchar LIKE 'h#%a%k%e'::mvarchar ESCAPE '#' AS "true"; +SELECT 'h%awkeye'::mvarchar NOT LIKE 'h#%a%k%e'::mvarchar ESCAPE '#' AS "false"; + +SELECT 'indio'::mvarchar LIKE '_ndio'::mvarchar ESCAPE '$' AS "true"; +SELECT 'indio'::mvarchar NOT LIKE '_ndio'::mvarchar ESCAPE '$' AS "false"; + +SELECT 'i_dio'::mvarchar LIKE 'i$_d_o'::mvarchar ESCAPE '$' AS "true"; +SELECT 'i_dio'::mvarchar NOT LIKE 'i$_d_o'::mvarchar ESCAPE '$' AS "false"; + +SELECT 'i_dio'::mvarchar LIKE 'i$_nd_o'::mvarchar ESCAPE '$' AS "false"; +SELECT 'i_dio'::mvarchar NOT LIKE 'i$_nd_o'::mvarchar ESCAPE '$' AS "true"; + +SELECT 'i_dio'::mvarchar LIKE 'i$_d%o'::mvarchar ESCAPE '$' AS "true"; +SELECT 'i_dio'::mvarchar NOT LIKE 'i$_d%o'::mvarchar ESCAPE '$' AS "false"; + +-- escape character same as pattern character +SELECT 'maca'::mvarchar LIKE 'm%aca' ESCAPE '%'::mvarchar AS "true"; +SELECT 'maca'::mvarchar NOT LIKE 'm%aca' ESCAPE '%'::mvarchar AS "false"; + +SELECT 'ma%a'::mvarchar LIKE 'm%a%%a' ESCAPE '%'::mvarchar AS "true"; +SELECT 'ma%a'::mvarchar NOT LIKE 'm%a%%a' ESCAPE '%'::mvarchar AS "false"; + +SELECT 'bear'::mvarchar LIKE 'b_ear' ESCAPE '_'::mvarchar AS "true"; +SELECT 'bear'::mvarchar NOT LIKE 'b_ear'::mvarchar ESCAPE '_' AS "false"; + +SELECT 'be_r'::mvarchar LIKE 'b_e__r' ESCAPE '_'::mvarchar AS "true"; +SELECT 'be_r'::mvarchar NOT LIKE 'b_e__r' ESCAPE '_'::mvarchar AS "false"; + +SELECT 'be_r'::mvarchar LIKE '__e__r' ESCAPE '_'::mvarchar AS "false"; +SELECT 'be_r'::mvarchar NOT LIKE '__e__r'::mvarchar ESCAPE '_' AS "true"; + +-- similar to + +SELECT 'abc'::mchar SIMILAR TO 'abc'::mchar AS "true"; +SELECT 'abc'::mchar SIMILAR TO 'a'::mchar AS "false"; +SELECT 'abc'::mchar SIMILAR TO '%(b|d)%'::mchar AS "true"; +SELECT 'abc'::mchar SIMILAR TO '(b|c)%'::mchar AS "false"; +SELECT 'h%'::mchar SIMILAR TO 'h#%'::mchar AS "false"; +SELECT 'h%'::mchar SIMILAR TO 'h#%'::mchar ESCAPE '#' AS "true"; + +SELECT 'abc'::mvarchar SIMILAR TO 'abc'::mvarchar AS "true"; +SELECT 'abc'::mvarchar SIMILAR TO 'a'::mvarchar AS "false"; +SELECT 'abc'::mvarchar SIMILAR TO '%(b|d)%'::mvarchar AS "true"; +SELECT 'abc'::mvarchar SIMILAR TO '(b|c)%'::mvarchar AS "false"; +SELECT 'h%'::mvarchar SIMILAR TO 'h#%'::mvarchar AS "false"; +SELECT 'h%'::mvarchar SIMILAR TO 'h#%'::mvarchar ESCAPE '#' AS "true"; + +-- index support + +SELECT * from ch where chcol like 'aB_d' order by chcol using &<; +SELECT * from ch where chcol like 'aB%d' order by chcol using &<; +SELECT * from ch where chcol like 'aB%' order by chcol using &<; +SELECT * from ch where chcol like '%BC%' order by chcol using &<; +set enable_seqscan = off; +explain (costs off) +SELECT * from ch where chcol like 'aB_d' order by chcol using &<; +SELECT * from ch where chcol like 'aB_d' order by chcol using &<; +SELECT * from ch where chcol like 'aB%d' order by chcol using &<; +SELECT * from ch where chcol like 'aB%' order by chcol using &<; +SELECT * from ch where chcol like '%BC%' order by chcol using &<; +set enable_seqscan = on; + + +create table testt (f1 mchar(10)); +insert into testt values ('Abc-000001'); +insert into testt values ('Abc-000002'); +insert into testt values ('0000000001'); +insert into testt values ('0000000002'); + +select f1 from testt where f1::mvarchar like E'Abc\\-%'::mvarchar; +select * from testt where f1::mchar like E'Abc\\-%'::mchar; +create index testindex on testt(f1); +set enable_seqscan=off; +explain (costs off) +select f1 from testt where f1::mvarchar like E'Abc\\-%'::mvarchar; +select f1 from testt where f1::mvarchar like E'Abc\\-%'::mvarchar; +select * from testt where f1::mchar like E'Abc\\-%'::mchar; +set enable_seqscan = on; +drop table testt; + +create table testt (f1 mvarchar(10)); +insert into testt values ('Abc-000001'); +insert into testt values ('Abc-000002'); +insert into testt values ('0000000001'); +insert into testt values ('0000000002'); + +select f1 from testt where f1::mvarchar like E'Abc\\-%'::mvarchar; +select * from testt where f1::mchar like E'Abc\\-%'::mchar; +select * from testt where f1::mchar like E'Abc\\- %'::mchar; +select * from testt where f1::mchar like E' %'::mchar; +create index testindex on testt(f1); +set enable_seqscan=off; +explain (costs off) +select f1 from testt where f1::mvarchar like E'Abc\\-%'::mvarchar; +select f1 from testt where f1::mvarchar like E'Abc\\-%'::mvarchar; +select * from testt where f1::mchar like E'Abc\\-%'::mchar; +select * from testt where f1::mchar like E'Abc\\- %'::mchar; +select * from testt where f1::mchar like E' %'::mchar; +set enable_seqscan = on; +drop table testt; + + +CREATE TABLE test ( code mchar(5) NOT NULL ); +insert into test values('1111 '); +insert into test values('111 '); +insert into test values('11 '); +insert into test values('1 '); + +SELECT * FROM test WHERE code LIKE ('% '); + +set escape_string_warning = off; +SELECT CASE WHEN ('_'::text SIMILAR TO '[\\_]'::text ESCAPE '\\'::text) THEN TRUE ELSE FALSE END ; +SELECT CASE WHEN ('_'::mchar SIMILAR TO '[\\_]'::mchar ESCAPE '\\'::mchar) THEN TRUE ELSE FALSE END ; +SELECT CASE WHEN ('_'::mvarchar SIMILAR TO '[\\_]'::mvarchar ESCAPE '\\'::mvarchar) THEN TRUE ELSE FALSE END ; +reset escape_string_warning; +reset standard_conforming_strings; + + diff --git a/contrib/mchar/sql/mchar.sql b/contrib/mchar/sql/mchar.sql new file mode 100644 index 00000000000..2ec0e659f4f --- /dev/null +++ b/contrib/mchar/sql/mchar.sql @@ -0,0 +1,90 @@ +-- I/O tests + +select '1'::mchar; +select '2 '::mchar; +select '10 '::mchar; + +select '1'::mchar(2); +select '2 '::mchar(2); +select '3 '::mchar(2); +select '10 '::mchar(2); + +select ' '::mchar(10); +select ' '::mchar; + +-- operations & functions + +select length('1'::mchar); +select length('2 '::mchar); +select length('10 '::mchar); + +select length('1'::mchar(2)); +select length('2 '::mchar(2)); +select length('3 '::mchar(2)); +select length('10 '::mchar(2)); + +select length(' '::mchar(10)); +select length(' '::mchar); + +select 'asd'::mchar(10) || '>'::mchar(10); +select length('asd'::mchar(10) || '>'::mchar(10)); +select 'asd'::mchar(2) || '>'::mchar(10); +select length('asd'::mchar(2) || '>'::mchar(10)); + +-- Comparisons + +select 'asdf'::mchar = 'aSdf'::mchar; +select 'asdf'::mchar = 'aSdf '::mchar; +select 'asdf'::mchar = 'aSdf 1'::mchar(4); +select 'asdf'::mchar = 'aSdf 1'::mchar(5); +select 'asdf'::mchar = 'aSdf 1'::mchar(6); +select 'asdf'::mchar(3) = 'aSdf 1'::mchar(5); +select 'asdf'::mchar(3) = 'aSdf 1'::mchar(3); + +select 'asdf'::mchar < 'aSdf'::mchar; +select 'asdf'::mchar < 'aSdf '::mchar; +select 'asdf'::mchar < 'aSdf 1'::mchar(4); +select 'asdf'::mchar < 'aSdf 1'::mchar(5); +select 'asdf'::mchar < 'aSdf 1'::mchar(6); + +select 'asdf'::mchar <= 'aSdf'::mchar; +select 'asdf'::mchar <= 'aSdf '::mchar; +select 'asdf'::mchar <= 'aSdf 1'::mchar(4); +select 'asdf'::mchar <= 'aSdf 1'::mchar(5); +select 'asdf'::mchar <= 'aSdf 1'::mchar(6); + +select 'asdf'::mchar >= 'aSdf'::mchar; +select 'asdf'::mchar >= 'aSdf '::mchar; +select 'asdf'::mchar >= 'aSdf 1'::mchar(4); +select 'asdf'::mchar >= 'aSdf 1'::mchar(5); +select 'asdf'::mchar >= 'aSdf 1'::mchar(6); + +select 'asdf'::mchar > 'aSdf'::mchar; +select 'asdf'::mchar > 'aSdf '::mchar; +select 'asdf'::mchar > 'aSdf 1'::mchar(4); +select 'asdf'::mchar > 'aSdf 1'::mchar(5); +select 'asdf'::mchar > 'aSdf 1'::mchar(6); + +select max(ch) from chvch; +select min(ch) from chvch; + +select substr('1234567890'::mchar, 3) = '34567890' as "34567890"; +select substr('1234567890'::mchar, 4, 3) = '456' as "456"; + +select lower('asdfASDF'::mchar); +select upper('asdfASDF'::mchar); + +select 'asd'::mchar == 'aSd'::mchar; +select 'asd'::mchar == 'aCd'::mchar; +select 'asd'::mchar == NULL; +select NULL == 'aCd'::mchar; +select NULL::mchar == NULL; + + +--Note: here we use different space symbols, be carefull to copy it! +select v, count(*) from +(values (1, '4 242'::mchar), (2, '4 242'), (3, 'aSDF'), (4, 'asdf')) as t(i,v) group by v; +set enable_hashagg=off; +select v, count(*) from +(values (1, '4 242'::mchar), (2, '4 242'), (3, 'aSDF'), (4, 'asdf')) as t(i,v) group by v; +reset enable_hashagg; diff --git a/contrib/mchar/sql/mm.sql b/contrib/mchar/sql/mm.sql new file mode 100644 index 00000000000..2e11b937040 --- /dev/null +++ b/contrib/mchar/sql/mm.sql @@ -0,0 +1,196 @@ +select 'asd'::mchar::mvarchar; +select 'asd '::mchar::mvarchar; +select 'asd'::mchar(2)::mvarchar; +select 'asd '::mchar(2)::mvarchar; +select 'asd'::mchar(5)::mvarchar; +select 'asd '::mchar(5)::mvarchar; +select 'asd'::mchar::mvarchar(2); +select 'asd '::mchar::mvarchar(2); +select 'asd'::mchar(2)::mvarchar(2); +select 'asd '::mchar(2)::mvarchar(2); +select 'asd'::mchar(5)::mvarchar(2); +select 'asd '::mchar(5)::mvarchar(2); +select 'asd'::mchar::mvarchar(5); +select 'asd '::mchar::mvarchar(5); +select 'asd'::mchar(2)::mvarchar(5); +select 'asd '::mchar(2)::mvarchar(5); +select 'asd'::mchar(5)::mvarchar(5); +select 'asd '::mchar(5)::mvarchar(5); + +select 'asd'::mvarchar::mchar; +select 'asd '::mvarchar::mchar; +select 'asd'::mvarchar(2)::mchar; +select 'asd '::mvarchar(2)::mchar; +select 'asd'::mvarchar(5)::mchar; +select 'asd '::mvarchar(5)::mchar; +select 'asd'::mvarchar::mchar(2); +select 'asd '::mvarchar::mchar(2); +select 'asd'::mvarchar(2)::mchar(2); +select 'asd '::mvarchar(2)::mchar(2); +select 'asd'::mvarchar(5)::mchar(2); +select 'asd '::mvarchar(5)::mchar(2); +select 'asd'::mvarchar::mchar(5); +select 'asd '::mvarchar::mchar(5); +select 'asd'::mvarchar(2)::mchar(5); +select 'asd '::mvarchar(2)::mchar(5); +select 'asd'::mvarchar(5)::mchar(5); +select 'asd '::mvarchar(5)::mchar(5); + +select 'asd'::mchar || '123'; +select 'asd'::mchar || '123'::mchar; +select 'asd'::mchar || '123'::mvarchar; + +select 'asd '::mchar || '123'; +select 'asd '::mchar || '123'::mchar; +select 'asd '::mchar || '123'::mvarchar; + +select 'asd '::mchar || '123 '; +select 'asd '::mchar || '123 '::mchar; +select 'asd '::mchar || '123 '::mvarchar; + + +select 'asd'::mvarchar || '123'; +select 'asd'::mvarchar || '123'::mchar; +select 'asd'::mvarchar || '123'::mvarchar; + +select 'asd '::mvarchar || '123'; +select 'asd '::mvarchar || '123'::mchar; +select 'asd '::mvarchar || '123'::mvarchar; + +select 'asd '::mvarchar || '123 '; +select 'asd '::mvarchar || '123 '::mchar; +select 'asd '::mvarchar || '123 '::mvarchar; + + +select 'asd'::mchar(2) || '123'; +select 'asd'::mchar(2) || '123'::mchar; +select 'asd'::mchar(2) || '123'::mvarchar; + + +select 'asd '::mchar(2) || '123'; +select 'asd '::mchar(2) || '123'::mchar; +select 'asd '::mchar(2) || '123'::mvarchar; + + +select 'asd '::mchar(2) || '123 '; +select 'asd '::mchar(2) || '123 '::mchar; +select 'asd '::mchar(2) || '123 '::mvarchar; + +select 'asd'::mvarchar(2) || '123'; +select 'asd'::mvarchar(2) || '123'::mchar; +select 'asd'::mvarchar(2) || '123'::mvarchar; + +select 'asd '::mvarchar(2) || '123'; +select 'asd '::mvarchar(2) || '123'::mchar; +select 'asd '::mvarchar(2) || '123'::mvarchar; + +select 'asd '::mvarchar(2) || '123 '; +select 'asd '::mvarchar(2) || '123 '::mchar; +select 'asd '::mvarchar(2) || '123 '::mvarchar; + +select 'asd'::mchar(4) || '143'; +select 'asd'::mchar(4) || '123'::mchar; +select 'asd'::mchar(4) || '123'::mvarchar; + +select 'asd '::mchar(4) || '123'; +select 'asd '::mchar(4) || '123'::mchar; +select 'asd '::mchar(4) || '123'::mvarchar; + +select 'asd '::mchar(4) || '123 '; +select 'asd '::mchar(4) || '123 '::mchar; +select 'asd '::mchar(4) || '123 '::mvarchar; + +select 'asd'::mvarchar(4) || '123'; +select 'asd'::mvarchar(4) || '123'::mchar; +select 'asd'::mvarchar(4) || '123'::mvarchar; + +select 'asd '::mvarchar(4) || '123'; +select 'asd '::mvarchar(4) || '123'::mchar; +select 'asd '::mvarchar(4) || '123'::mvarchar; + +select 'asd '::mvarchar(4) || '123 '; +select 'asd '::mvarchar(4) || '123 '::mchar; +select 'asd '::mvarchar(4) || '123 '::mvarchar; + + +select 'asd '::mvarchar(4) || '123 '::mchar(4); +select 'asd '::mvarchar(4) || '123 '::mvarchar(4); +select 'asd '::mvarchar(4) || '123'::mchar(4); +select 'asd '::mvarchar(4) || '123'::mvarchar(4); + + +select 1 where 'f'::mchar='F'::mvarchar; +select 1 where 'f'::mchar='F '::mvarchar; +select 1 where 'f '::mchar='F'::mvarchar; +select 1 where 'f '::mchar='F '::mvarchar; + +select 1 where 'f'::mchar='F'::mvarchar(2); +select 1 where 'f'::mchar='F '::mvarchar(2); +select 1 where 'f '::mchar='F'::mvarchar(2); +select 1 where 'f '::mchar='F '::mvarchar(2); + +select 1 where 'f'::mchar(2)='F'::mvarchar; +select 1 where 'f'::mchar(2)='F '::mvarchar; +select 1 where 'f '::mchar(2)='F'::mvarchar; +select 1 where 'f '::mchar(2)='F '::mvarchar; + +select 1 where 'f'::mchar(2)='F'::mvarchar(2); +select 1 where 'f'::mchar(2)='F '::mvarchar(2); +select 1 where 'f '::mchar(2)='F'::mvarchar(2); +select 1 where 'f '::mchar(2)='F '::mvarchar(2); + +select 1 where 'foo'::mchar='FOO'::mvarchar; +select 1 where 'foo'::mchar='FOO '::mvarchar; +select 1 where 'foo '::mchar='FOO'::mvarchar; +select 1 where 'foo '::mchar='FOO '::mvarchar; + +select 1 where 'foo'::mchar='FOO'::mvarchar(2); +select 1 where 'foo'::mchar='FOO '::mvarchar(2); +select 1 where 'foo '::mchar='FOO'::mvarchar(2); +select 1 where 'foo '::mchar='FOO '::mvarchar(2); + +select 1 where 'foo'::mchar(2)='FOO'::mvarchar; +select 1 where 'foo'::mchar(2)='FOO '::mvarchar; +select 1 where 'foo '::mchar(2)='FOO'::mvarchar; +select 1 where 'foo '::mchar(2)='FOO '::mvarchar; + +select 1 where 'foo'::mchar(2)='FOO'::mvarchar(2); +select 1 where 'foo'::mchar(2)='FOO '::mvarchar(2); +select 1 where 'foo '::mchar(2)='FOO'::mvarchar(2); +select 1 where 'foo '::mchar(2)='FOO '::mvarchar(2); + +Select 'f'::mchar(1) Union Select 'o'::mvarchar(1); +Select 'f'::mvarchar(1) Union Select 'o'::mchar(1); + +select * from chvch where ch=vch; + +select ch.* from ch, (select 'dEfg'::mvarchar as q) as p where chcol > p.q; +create index qq on ch (chcol); +set enable_seqscan=off; +select ch.* from ch, (select 'dEfg'::mvarchar as q) as p where chcol > p.q; +set enable_seqscan=on; + + +--\copy chvch to 'results/chvch.dump' binary +--truncate table chvch; +--\copy chvch from 'results/chvch.dump' binary + +--test joins +CREATE TABLE a (mchar2 MCHAR(2) NOT NULL); +CREATE TABLE c (mvarchar255 mvarchar NOT NULL); +SELECT * FROM a, c WHERE mchar2 = mvarchar255; +SELECT * FROM a, c WHERE mvarchar255 = mchar2; +DROP TABLE a; +DROP TABLE c; + +select * from (values + ('е'::mchar),('ё'),('еа'),('еб'),('ее'),('еж'),('ёа'),('ёб'),('ёё'),('ёж'),('ёе'),('её')) + z order by 1; + +select 'ё'::mchar = 'е'; +select 'Ё'::mchar = 'Е'; +select 'й'::mchar = 'и'; +select 'Й'::mchar = 'И'; + +select mvarchar_icase_cmp('ёа','еб'), mvarchar_icase_cmp('еб','ё'), + mvarchar_icase_cmp('ё', 'ёа'); diff --git a/contrib/mchar/sql/mvarchar.sql b/contrib/mchar/sql/mvarchar.sql new file mode 100644 index 00000000000..91b0981075d --- /dev/null +++ b/contrib/mchar/sql/mvarchar.sql @@ -0,0 +1,82 @@ +-- I/O tests + +select '1'::mvarchar; +select '2 '::mvarchar; +select '10 '::mvarchar; + +select '1'::mvarchar(2); +select '2 '::mvarchar(2); +select '3 '::mvarchar(2); +select '10 '::mvarchar(2); + +select ' '::mvarchar(10); +select ' '::mvarchar; + +-- operations & functions + +select length('1'::mvarchar); +select length('2 '::mvarchar); +select length('10 '::mvarchar); + +select length('1'::mvarchar(2)); +select length('2 '::mvarchar(2)); +select length('3 '::mvarchar(2)); +select length('10 '::mvarchar(2)); + +select length(' '::mvarchar(10)); +select length(' '::mvarchar); + +select 'asd'::mvarchar(10) || '>'::mvarchar(10); +select length('asd'::mvarchar(10) || '>'::mvarchar(10)); +select 'asd'::mvarchar(2) || '>'::mvarchar(10); +select length('asd'::mvarchar(2) || '>'::mvarchar(10)); + +-- Comparisons + +select 'asdf'::mvarchar = 'aSdf'::mvarchar; +select 'asdf'::mvarchar = 'aSdf '::mvarchar; +select 'asdf'::mvarchar = 'aSdf 1'::mvarchar(4); +select 'asdf'::mvarchar = 'aSdf 1'::mvarchar(5); +select 'asdf'::mvarchar = 'aSdf 1'::mvarchar(6); +select 'asdf'::mvarchar(3) = 'aSdf 1'::mvarchar(5); +select 'asdf'::mvarchar(3) = 'aSdf 1'::mvarchar(3); + +select 'asdf'::mvarchar < 'aSdf'::mvarchar; +select 'asdf'::mvarchar < 'aSdf '::mvarchar; +select 'asdf'::mvarchar < 'aSdf 1'::mvarchar(4); +select 'asdf'::mvarchar < 'aSdf 1'::mvarchar(5); +select 'asdf'::mvarchar < 'aSdf 1'::mvarchar(6); + +select 'asdf'::mvarchar <= 'aSdf'::mvarchar; +select 'asdf'::mvarchar <= 'aSdf '::mvarchar; +select 'asdf'::mvarchar <= 'aSdf 1'::mvarchar(4); +select 'asdf'::mvarchar <= 'aSdf 1'::mvarchar(5); +select 'asdf'::mvarchar <= 'aSdf 1'::mvarchar(6); + +select 'asdf'::mvarchar >= 'aSdf'::mvarchar; +select 'asdf'::mvarchar >= 'aSdf '::mvarchar; +select 'asdf'::mvarchar >= 'aSdf 1'::mvarchar(4); +select 'asdf'::mvarchar >= 'aSdf 1'::mvarchar(5); +select 'asdf'::mvarchar >= 'aSdf 1'::mvarchar(6); + +select 'asdf'::mvarchar > 'aSdf'::mvarchar; +select 'asdf'::mvarchar > 'aSdf '::mvarchar; +select 'asdf'::mvarchar > 'aSdf 1'::mvarchar(4); +select 'asdf'::mvarchar > 'aSdf 1'::mvarchar(5); +select 'asdf'::mvarchar > 'aSdf 1'::mvarchar(6); + +select max(vch) from chvch; +select min(vch) from chvch; + +select substr('1234567890'::mvarchar, 3) = '34567890' as "34567890"; +select substr('1234567890'::mvarchar, 4, 3) = '456' as "456"; + +select lower('asdfASDF'::mvarchar); +select upper('asdfASDF'::mvarchar); + +select 'asd'::mvarchar == 'aSd'::mvarchar; +select 'asd'::mvarchar == 'aCd'::mvarchar; +select 'asd'::mvarchar == NULL; +select NULL == 'aCd'::mvarchar; +select NULL::mvarchar == NULL; + diff --git a/contrib/online_analyze/COPYRIGHT b/contrib/online_analyze/COPYRIGHT new file mode 100644 index 00000000000..75fea1f35d6 --- /dev/null +++ b/contrib/online_analyze/COPYRIGHT @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2011 Teodor Sigaev <teodor@sigaev.ru> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY CONTRIBUTORS ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + diff --git a/contrib/online_analyze/Makefile b/contrib/online_analyze/Makefile new file mode 100644 index 00000000000..333add2b09b --- /dev/null +++ b/contrib/online_analyze/Makefile @@ -0,0 +1,16 @@ +MODULE_big = online_analyze +OBJS = online_analyze.o +#DATA_built = online_analyze.sql +DOCS = README.online_analyze +#REGRESS = online_analyze + +ifdef USE_PGXS +PGXS := $(shell pg_config --pgxs) +include $(PGXS) +else +subdir = contrib/online_analyze +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif + diff --git a/contrib/online_analyze/README.online_analyze b/contrib/online_analyze/README.online_analyze new file mode 100644 index 00000000000..d72f17db424 --- /dev/null +++ b/contrib/online_analyze/README.online_analyze @@ -0,0 +1,46 @@ +Module makes an analyze call immediately after INSERT/UPDATE/DELETE/SELECT INTO +for affected table(s). + +Supported versions of PostgreSQL: 8.4.*, 9.0.*, 9.1.*, 9.2.*, 9.3.*, 9.4*, 9.5*, + 9.6* + +Usage: LOAD 'online_analyze'; + +Custom variables (defaults values are shown): +online_analyze.enable = on + Enables on-line analyze + +online_analyze.local_tracking = off + Per backend tracking for temp tables (do not use system statistic) + +online_analyze.verbose = on + Execute ANALYZE VERBOSE + +online_analyze.scale_factor = 0.1 + Fraction of table size to start on-line analyze (similar to + autovacuum_analyze_scale_factor) + +online_analyze.threshold = 50 + Min number of row updates before on-line analyze (similar to + autovacuum_analyze_threshold) + +online_analyze.min_interval = 10000 + Minimum time interval between analyze call per table (in milliseconds) + +online_analyze.lower_limit = 0 + Min number of rows in table to analyze + +online_analyze.table_type = "all" + Type(s) of table for online analyze: all, persistent, temporary, none + +online_analyze.exclude_tables = "" + List of tables which will not online analyze + +online_analyze.include_tables = "" + List of tables which will online analyze + online_analyze.include_tables overwrites online_analyze.exclude_tables. + +online_analyze.capacity_threshold = 100000 + Maximum number of temporary tables to store in local cache + +Author: Teodor Sigaev <teodor@sigaev.ru> diff --git a/contrib/online_analyze/online_analyze.c b/contrib/online_analyze/online_analyze.c new file mode 100644 index 00000000000..6ffcc30997d --- /dev/null +++ b/contrib/online_analyze/online_analyze.c @@ -0,0 +1,1370 @@ +/* + * Copyright (c) 2011 Teodor Sigaev <teodor@sigaev.ru> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY CONTRIBUTORS ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "postgres.h" + +#include "pgstat.h" +#include "miscadmin.h" +#include "access/transam.h" +#include "access/xact.h" +#include "catalog/namespace.h" +#include "commands/vacuum.h" +#include "executor/executor.h" +#include "nodes/nodes.h" +#include "nodes/parsenodes.h" +#include "storage/bufmgr.h" +#include "utils/builtins.h" +#include "utils/hsearch.h" +#include "utils/memutils.h" +#include "utils/lsyscache.h" +#include "utils/guc.h" +#if PG_VERSION_NUM >= 90200 +#include "catalog/pg_class.h" +#include "nodes/primnodes.h" +#include "tcop/utility.h" +#include "utils/rel.h" +#include "utils/relcache.h" +#include "utils/timestamp.h" +#if PG_VERSION_NUM >= 90500 +#include "nodes/makefuncs.h" +#if PG_VERSION_NUM >= 100000 +#include "utils/varlena.h" +#include "utils/regproc.h" +#if PG_VERSION_NUM >= 130000 +#include "common/hashfn.h" +#endif +#endif +#endif +#endif + +#ifdef PG_MODULE_MAGIC +PG_MODULE_MAGIC; +#endif + +static bool online_analyze_enable = true; +static bool online_analyze_local_tracking = false; +static bool online_analyze_verbose = true; +static double online_analyze_scale_factor = 0.1; +static int online_analyze_threshold = 50; +static int online_analyze_capacity_threshold = 100000; +static double online_analyze_min_interval = 10000; +static int online_analyze_lower_limit = 0; + +static ExecutorEnd_hook_type oldExecutorEndHook = NULL; +#if PG_VERSION_NUM >= 90200 +static ProcessUtility_hook_type oldProcessUtilityHook = NULL; +#endif + +#if PG_VERSION_NUM >= 120000 +#define VACOPT_NOWAIT VACOPT_SKIP_LOCKED +#endif + +typedef enum CmdKind +{ + CK_SELECT = CMD_SELECT, + CK_UPDATE = CMD_UPDATE, + CK_INSERT = CMD_INSERT, + CK_DELETE = CMD_DELETE, + CK_TRUNCATE, + CK_FASTTRUNCATE, + CK_CREATE, + CK_ANALYZE, + CK_VACUUM +} CmdKind; + + +typedef enum +{ + OATT_ALL = 0x03, + OATT_PERSISTENT = 0x01, + OATT_TEMPORARY = 0x02, + OATT_NONE = 0x00 +} OnlineAnalyzeTableType; + +static const struct config_enum_entry online_analyze_table_type_options[] = +{ + {"all", OATT_ALL, false}, + {"persistent", OATT_PERSISTENT, false}, + {"temporary", OATT_TEMPORARY, false}, + {"none", OATT_NONE, false}, + {NULL, 0, false}, +}; + +static int online_analyze_table_type = (int)OATT_ALL; + +typedef struct TableList { + int nTables; + Oid *tables; + char *tableStr; + bool inited; +} TableList; + +static TableList excludeTables = {0, NULL, NULL, false}; +static TableList includeTables = {0, NULL, NULL, false}; + +typedef struct OnlineAnalyzeTableStat { + Oid tableid; + bool rereadStat; + PgStat_Counter n_tuples; + PgStat_Counter changes_since_analyze; + TimestampTz autovac_analyze_timestamp; + TimestampTz analyze_timestamp; +} OnlineAnalyzeTableStat; + +static MemoryContext onlineAnalyzeMemoryContext = NULL; +static HTAB *relstats = NULL; + +static void relstatsInit(void); + +#if PG_VERSION_NUM < 100000 +static int +oid_cmp(const void *a, const void *b) +{ + if (*(Oid*)a == *(Oid*)b) + return 0; + return (*(Oid*)a > *(Oid*)b) ? 1 : -1; +} +#endif + +static const char * +tableListAssign(const char * newval, bool doit, TableList *tbl) +{ + char *rawname; + List *namelist; + ListCell *l; + Oid *newOids = NULL; + int nOids = 0, + i = 0; + + rawname = pstrdup(newval); + + if (!SplitIdentifierString(rawname, ',', &namelist)) + goto cleanup; + + /* + * follow work could be done only in normal processing because of + * accsess to system catalog + */ + if (MyBackendId == InvalidBackendId || !IsUnderPostmaster || + !IsTransactionState()) + { + includeTables.inited = false; + excludeTables.inited = false; + return newval; + } + + if (doit) + { + nOids = list_length(namelist); + newOids = malloc(sizeof(Oid) * (nOids+1)); + if (!newOids) + elog(ERROR,"could not allocate %d bytes", + (int)(sizeof(Oid) * (nOids+1))); + } + + foreach(l, namelist) + { + char *curname = (char *) lfirst(l); +#if PG_VERSION_NUM >= 90200 + Oid relOid = RangeVarGetRelid(makeRangeVarFromNameList( + stringToQualifiedNameList(curname)), NoLock, true); +#else + Oid relOid = RangeVarGetRelid(makeRangeVarFromNameList( + stringToQualifiedNameList(curname)), true); +#endif + + if (relOid == InvalidOid) + { +#if PG_VERSION_NUM >= 90100 + if (doit == false) +#endif + elog(WARNING,"'%s' does not exist", curname); + continue; + } + else if ( get_rel_relkind(relOid) != RELKIND_RELATION ) + { +#if PG_VERSION_NUM >= 90100 + if (doit == false) +#endif + elog(WARNING,"'%s' is not an table", curname); + continue; + } + else if (doit) + { + newOids[i++] = relOid; + } + } + + if (doit) + { + tbl->nTables = i; + if (tbl->tables) + free(tbl->tables); + tbl->tables = newOids; + if (tbl->nTables > 1) + qsort(tbl->tables, tbl->nTables, sizeof(tbl->tables[0]), oid_cmp); + } + + pfree(rawname); + list_free(namelist); + + return newval; + +cleanup: + if (newOids) + free(newOids); + pfree(rawname); + list_free(namelist); + return NULL; +} + +#if PG_VERSION_NUM >= 90100 +static bool +excludeTablesCheck(char **newval, void **extra, GucSource source) +{ + char *val; + + val = (char*)tableListAssign(*newval, false, &excludeTables); + + if (val) + { + *newval = val; + return true; + } + + return false; +} + +static void +excludeTablesAssign(const char *newval, void *extra) +{ + tableListAssign(newval, true, &excludeTables); +} + +static bool +includeTablesCheck(char **newval, void **extra, GucSource source) +{ + char *val; + + val = (char*)tableListAssign(*newval, false, &includeTables); + + if (val) + { + *newval = val; + return true; + } + + return false; +} + +static void +includeTablesAssign(const char *newval, void *extra) +{ + tableListAssign(newval, true, &includeTables); +} + +#else /* PG_VERSION_NUM < 90100 */ + +static const char * +excludeTablesAssign(const char * newval, bool doit, GucSource source) +{ + return tableListAssign(newval, doit, &excludeTables); +} + +static const char * +includeTablesAssign(const char * newval, bool doit, GucSource source) +{ + return tableListAssign(newval, doit, &includeTables); +} + +#endif + +static void +lateInit() +{ + TableList *tl[] = {&includeTables, &excludeTables}; + int i; + + if (MyBackendId == InvalidBackendId || !IsUnderPostmaster || + !IsTransactionState()) + return; /* we aren't in connected state */ + + for(i=0; i<lengthof(tl); i++) + { + TableList *tbl = tl[i]; + + if (tbl->inited == false) + tableListAssign(tbl->tableStr, true, tbl); + tbl->inited = true; + } +} + +static const char* +tableListShow(TableList *tbl) +{ + char *val, *ptr; + int i, + len; + + lateInit(); + + len = 1 /* \0 */ + tbl->nTables * (2 * NAMEDATALEN + 2 /* ', ' */ + 1 /* . */); + ptr = val = palloc(len); + *ptr ='\0'; + for(i=0; i<tbl->nTables; i++) + { + char *relname = get_rel_name(tbl->tables[i]); + Oid nspOid = get_rel_namespace(tbl->tables[i]); + char *nspname = get_namespace_name(nspOid); + + if ( relname == NULL || nspOid == InvalidOid || nspname == NULL ) + continue; + + ptr += snprintf(ptr, len - (ptr - val), "%s%s.%s", + (i==0) ? "" : ", ", + nspname, relname); + } + + return val; +} + +static const char* +excludeTablesShow(void) +{ + return tableListShow(&excludeTables); +} + +static const char* +includeTablesShow(void) +{ + return tableListShow(&includeTables); +} + +static bool +matchOid(TableList *tbl, Oid oid) +{ + Oid *StopLow = tbl->tables, + *StopHigh = tbl->tables + tbl->nTables, + *StopMiddle; + + /* Loop invariant: StopLow <= val < StopHigh */ + while (StopLow < StopHigh) + { + StopMiddle = StopLow + ((StopHigh - StopLow) >> 1); + + if (*StopMiddle == oid) + return true; + else if (*StopMiddle < oid) + StopLow = StopMiddle + 1; + else + StopHigh = StopMiddle; + } + + return false; +} + +#if PG_VERSION_NUM >= 90500 +static RangeVar* +makeRangeVarFromOid(Oid relOid) +{ + return makeRangeVar( + get_namespace_name(get_rel_namespace(relOid)), + get_rel_name(relOid), + -1 + ); + +} +#endif + +static void +makeAnalyze(Oid relOid, CmdKind operation, int64 naffected) +{ + TimestampTz now = GetCurrentTimestamp(); + Relation rel; + OnlineAnalyzeTableType reltype; + bool found = false, + newTable = false; + OnlineAnalyzeTableStat *rstat, + dummyrstat; + PgStat_StatTabEntry *tabentry = NULL; + + if (relOid == InvalidOid) + return; + + if (naffected == 0) + /* return if there is no changes */ + return; + else if (naffected < 0) + /* number if affected rows is unknown */ + naffected = 0; + + rel = RelationIdGetRelation(relOid); + if (rel->rd_rel->relkind != RELKIND_RELATION) + { + RelationClose(rel); + return; + } + + reltype = +#if PG_VERSION_NUM >= 90100 + (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP) +#else + (rel->rd_istemp || rel->rd_islocaltemp) +#endif + ? OATT_TEMPORARY : OATT_PERSISTENT; + + RelationClose(rel); + + /* + * includeTables overwrites excludeTables + */ + switch(online_analyze_table_type) + { + case OATT_ALL: + if (get_rel_relkind(relOid) != RELKIND_RELATION || + (matchOid(&excludeTables, relOid) == true && + matchOid(&includeTables, relOid) == false)) + return; + break; + case OATT_NONE: + if (get_rel_relkind(relOid) != RELKIND_RELATION || + matchOid(&includeTables, relOid) == false) + return; + break; + case OATT_TEMPORARY: + case OATT_PERSISTENT: + default: + /* + * skip analyze if relation's type doesn't not match + * online_analyze_table_type + */ + if ((online_analyze_table_type & reltype) == 0 || + matchOid(&excludeTables, relOid) == true) + { + if (matchOid(&includeTables, relOid) == false) + return; + } + break; + } + + /* + * Do not store data about persistent table in local memory because we + * could not track changes of them: they could be changed by another + * backends. So always get a pgstat table entry. + */ + if (reltype == OATT_TEMPORARY) + rstat = hash_search(relstats, &relOid, HASH_ENTER, &found); + else + rstat = &dummyrstat; /* found == false for following if */ + + if (!found) + { + MemSet(rstat, 0, sizeof(*rstat)); + rstat->tableid = relOid; + newTable = true; + } + + if (operation == CK_VACUUM) + { + /* force reread because vacuum could change n_tuples */ + rstat->rereadStat = true; + return; + } + else if (operation == CK_ANALYZE) + { + /* only analyze */ + rstat->changes_since_analyze = 0; + rstat->analyze_timestamp = now; + if (newTable) + rstat->rereadStat = true; + return; + } + + Assert(rstat->tableid == relOid); + + if ( + /* do not reread data if it was a truncation */ + operation != CK_TRUNCATE && operation != CK_FASTTRUNCATE && + /* read for persistent table and for temp teble if it allowed */ + (reltype == OATT_PERSISTENT || online_analyze_local_tracking == false) && + /* read only for new table or we know that it's needed */ + (newTable == true || rstat->rereadStat == true) + ) + { + rstat->rereadStat = false; + + tabentry = pgstat_fetch_stat_tabentry(relOid); + + if (tabentry) + { + rstat->n_tuples = tabentry->n_dead_tuples + tabentry->n_live_tuples; + rstat->changes_since_analyze = +#if PG_VERSION_NUM >= 90000 + tabentry->changes_since_analyze; +#else + tabentry->n_live_tuples + tabentry->n_dead_tuples - + tabentry->last_anl_tuples; +#endif + rstat->autovac_analyze_timestamp = + tabentry->autovac_analyze_timestamp; + rstat->analyze_timestamp = tabentry->analyze_timestamp; + } + } + + if (newTable || + /* force analyze after truncate, fasttruncate already did analyze */ + operation == CK_TRUNCATE || ( + /* do not analyze too often, if both stamps are exceeded the go */ + TimestampDifferenceExceeds(rstat->analyze_timestamp, now, online_analyze_min_interval) && + TimestampDifferenceExceeds(rstat->autovac_analyze_timestamp, now, online_analyze_min_interval) && + /* do not analyze too small tables */ + rstat->n_tuples + rstat->changes_since_analyze + naffected > online_analyze_lower_limit && + /* be in sync with relation_needs_vacanalyze */ + ((double)(rstat->changes_since_analyze + naffected)) >= + online_analyze_scale_factor * ((double)rstat->n_tuples) + + (double)online_analyze_threshold)) + { +#if PG_VERSION_NUM < 90500 + VacuumStmt vacstmt; +#else + VacuumParams vacstmt; +#endif + TimestampTz startStamp, endStamp; + int flags; + +#ifdef PGPRO_EE + /* ATX is not compatible with online_analyze */ + if (getNestLevelATX() != 0) + return; +#endif + + memset(&startStamp, 0, sizeof(startStamp)); /* keep compiler quiet */ + + memset(&vacstmt, 0, sizeof(vacstmt)); + + vacstmt.freeze_min_age = -1; + vacstmt.freeze_table_age = -1; /* ??? */ + +#if PG_VERSION_NUM < 90500 + vacstmt.type = T_VacuumStmt; + vacstmt.relation = NULL; + vacstmt.va_cols = NIL; +#if PG_VERSION_NUM >= 90000 + vacstmt.options = VACOPT_ANALYZE; + if (online_analyze_verbose) + vacstmt.options |= VACOPT_VERBOSE; +#else + vacstmt.vacuum = vacstmt.full = false; + vacstmt.analyze = true; + vacstmt.verbose = online_analyze_verbose; +#endif +#else + vacstmt.multixact_freeze_min_age = -1; + vacstmt.multixact_freeze_table_age = -1; + vacstmt.log_min_duration = -1; +#endif + + + if (online_analyze_verbose) + startStamp = GetCurrentTimestamp(); + + flags = VACOPT_ANALYZE | VACOPT_NOWAIT | + ((online_analyze_verbose) ? VACOPT_VERBOSE : 0); + +#if PG_VERSION_NUM >= 120000 + vacstmt.options = flags; +#endif + analyze_rel(relOid, +#if PG_VERSION_NUM < 90500 + &vacstmt +#if PG_VERSION_NUM >= 90018 + , true +#endif + , GetAccessStrategy(BAS_VACUUM) +#if (PG_VERSION_NUM >= 90000) && (PG_VERSION_NUM < 90004) + , true +#endif +#else + makeRangeVarFromOid(relOid), +#if PG_VERSION_NUM < 120000 + flags, +#endif + &vacstmt, NULL, true, GetAccessStrategy(BAS_VACUUM) +#endif + ); + + /* Make changes visible to subsequent calls */ + CommandCounterIncrement(); + + if (online_analyze_verbose) + { + long secs; + int microsecs; + + endStamp = GetCurrentTimestamp(); + TimestampDifference(startStamp, endStamp, &secs, µ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; + + lateInit(); + +#if PG_VERSION_NUM >= 90200 + if (online_analyze_enable && + (constval = isFastTruncateCall(queryDesc)) != NULL) + { + Datum tblnamed = constval->constvalue; + char *tblname = text_to_cstring(DatumGetTextP(tblnamed)); + RangeVar *tblvar = + makeRangeVarFromNameList(stringToQualifiedNameList(tblname)); + + makeAnalyze(RangeVarGetRelid(tblvar, + NoLock, + false), + CK_FASTTRUNCATE, -1); + } +#endif + + if (online_analyze_enable && queryDesc->plannedstmt && + (queryDesc->operation == CMD_INSERT || + queryDesc->operation == CMD_UPDATE || + queryDesc->operation == CMD_DELETE +#if PG_VERSION_NUM < 90200 + || (queryDesc->operation == CMD_SELECT && + queryDesc->plannedstmt->intoClause) +#endif + )) + { +#if PG_VERSION_NUM < 90200 + if (queryDesc->operation == CMD_SELECT) + { + Oid relOid = RangeVarGetRelid(queryDesc->plannedstmt->intoClause->rel, true); + + makeAnalyze(relOid, queryDesc->operation, naffected); + } + else +#endif + if (queryDesc->plannedstmt->resultRelations && + queryDesc->plannedstmt->rtable) + { + ListCell *l; + + foreach(l, queryDesc->plannedstmt->resultRelations) + { + int n = lfirst_int(l); + RangeTblEntry *rte = list_nth(queryDesc->plannedstmt->rtable, n-1); + + if (rte->rtekind == RTE_RELATION) + makeAnalyze(rte->relid, (CmdKind)queryDesc->operation, naffected); + } + } + } + + if (oldExecutorEndHook) + oldExecutorEndHook(queryDesc); + else + standard_ExecutorEnd(queryDesc); +} + +static List *toremove = NIL; + +/* + * removeTable called on transaction end, see call RegisterXactCallback() below + */ +static void +removeTable(XactEvent event, void *arg) +{ + ListCell *cell; + + switch(event) + { + case XACT_EVENT_COMMIT: + break; + case XACT_EVENT_ABORT: + toremove = NIL; + default: + return; + } + + foreach(cell, toremove) + { + Oid relOid = lfirst_oid(cell); + + hash_search(relstats, &relOid, HASH_REMOVE, NULL); + } + + toremove = NIL; +} + +#if PG_VERSION_NUM >= 120000 +static int +parse_vacuum_opt(VacuumStmt *vacstmt) +{ + int options = vacstmt->is_vacuumcmd ? VACOPT_VACUUM : VACOPT_ANALYZE; + ListCell *lc; + + foreach(lc, vacstmt->options) + { + DefElem *opt = (DefElem *) lfirst(lc); + + /* Parse common options for VACUUM and ANALYZE */ + if (strcmp(opt->defname, "verbose") == 0) + options |= VACOPT_VERBOSE; + else if (strcmp(opt->defname, "skip_locked") == 0) + options |= VACOPT_SKIP_LOCKED; + else if (strcmp(opt->defname, "analyze") == 0) + options |= VACOPT_ANALYZE; + else if (strcmp(opt->defname, "freeze") == 0) + options |= VACOPT_FREEZE; + else if (strcmp(opt->defname, "full") == 0) + options |= VACOPT_FULL; + else if (strcmp(opt->defname, "disable_page_skipping") == 0) + options |= VACOPT_DISABLE_PAGE_SKIPPING; + } + + return options; +} +#endif + + +#if PG_VERSION_NUM >= 90200 +static void +onlineAnalyzeHookerUtility( +#if PG_VERSION_NUM >= 100000 + PlannedStmt *pstmt, +#else + Node *parsetree, +#endif + const char *queryString, +#if PG_VERSION_NUM >= 140000 + bool readOnlyTree, +#endif +#if PG_VERSION_NUM >= 90300 + ProcessUtilityContext context, ParamListInfo params, +#if PG_VERSION_NUM >= 100000 + QueryEnvironment *queryEnv, +#endif +#else + ParamListInfo params, bool isTopLevel, +#endif + DestReceiver *dest, +#if PG_VERSION_NUM >= 130000 + QueryCompletion *completionTag +#else + char *completionTag +#endif +) { + List *tblnames = NIL; + CmdKind op = CK_INSERT; +#if PG_VERSION_NUM >= 100000 + Node *parsetree = NULL; + + if (pstmt->commandType == CMD_UTILITY) + parsetree = pstmt->utilityStmt; +#endif + + lateInit(); + + if (parsetree && online_analyze_enable) + { + if (IsA(parsetree, CreateTableAsStmt) && + ((CreateTableAsStmt*)parsetree)->into) + { + tblnames = + list_make1((RangeVar*)copyObject(((CreateTableAsStmt*)parsetree)->into->rel)); + op = CK_CREATE; + } + else if (IsA(parsetree, TruncateStmt)) + { + tblnames = list_copy(((TruncateStmt*)parsetree)->relations); + op = CK_TRUNCATE; + } + else if (IsA(parsetree, DropStmt) && + ((DropStmt*)parsetree)->removeType == OBJECT_TABLE) + { + ListCell *cell; + + foreach(cell, ((DropStmt*)parsetree)->objects) + { + List *relname = (List *) lfirst(cell); + RangeVar *rel = makeRangeVarFromNameList(relname); + Oid relOid = RangeVarGetRelid(rel, NoLock, true); + + if (OidIsValid(relOid)) + { + MemoryContext ctx; + + ctx = MemoryContextSwitchTo(TopTransactionContext); + toremove = lappend_oid(toremove, relOid); + MemoryContextSwitchTo(ctx); + } + } + } + else if (IsA(parsetree, VacuumStmt)) + { + VacuumStmt *vac = (VacuumStmt*)parsetree; + int options = +#if PG_VERSION_NUM >= 120000 + parse_vacuum_opt(vac) +#else + vac->options +#endif + ; + + +#if PG_VERSION_NUM >= 110000 + tblnames = vac->rels; +#else + if (vac->relation) + tblnames = list_make1(vac->relation); +#endif + + if (options & (VACOPT_VACUUM | VACOPT_FULL | VACOPT_FREEZE)) + { + /* optionally with analyze */ + op = CK_VACUUM; + + /* drop all collected stat */ + if (tblnames == NIL) + relstatsInit(); + } + else if (options & VACOPT_ANALYZE) + { + op = CK_ANALYZE; + + /* should reset all counters */ + if (tblnames == NIL) + { + HASH_SEQ_STATUS hs; + OnlineAnalyzeTableStat *rstat; + TimestampTz now = GetCurrentTimestamp(); + + hash_seq_init(&hs, relstats); + + while((rstat = hash_seq_search(&hs)) != NULL) + { + rstat->changes_since_analyze = 0; + rstat->analyze_timestamp = now; + } + } + } + else + tblnames = NIL; + } + } + +#if PG_VERSION_NUM >= 100000 +#define parsetree pstmt +#endif + + if (oldProcessUtilityHook) + oldProcessUtilityHook(parsetree, queryString, +#if PG_VERSION_NUM >= 140000 + readOnlyTree, +#endif +#if PG_VERSION_NUM >= 90300 + context, params, +#if PG_VERSION_NUM >= 100000 + queryEnv, +#endif +#else + params, isTopLevel, +#endif + dest, completionTag); + else + standard_ProcessUtility(parsetree, queryString, +#if PG_VERSION_NUM >= 140000 + readOnlyTree, +#endif +#if PG_VERSION_NUM >= 90300 + context, params, +#if PG_VERSION_NUM >= 100000 + queryEnv, +#endif +#else + params, isTopLevel, +#endif + dest, completionTag); + +#if PG_VERSION_NUM >= 100000 +#undef parsetree +#endif + + if (tblnames) { + ListCell *l; + + foreach(l, tblnames) + { + RangeVar *tblname = +#if PG_VERSION_NUM >= 110000 + (IsA(lfirst(l), VacuumRelation)) ? + ((VacuumRelation*)lfirst(l))->relation : +#endif + (RangeVar*)lfirst(l); + Oid tblOid; + + Assert(IsA(tblname, RangeVar)); + + tblOid = RangeVarGetRelid(tblname, NoLock, true); + makeAnalyze(tblOid, op, -1); + } + } +} +#endif + + +static void +relstatsInit(void) +{ + HASHCTL hash_ctl; + int flags = 0; + + MemSet(&hash_ctl, 0, sizeof(hash_ctl)); + + hash_ctl.hash = oid_hash; + flags |= HASH_FUNCTION; + + if (onlineAnalyzeMemoryContext) + { + Assert(relstats != NULL); + MemoryContextReset(onlineAnalyzeMemoryContext); + } + else + { + Assert(relstats == NULL); + +#if PG_VERSION_NUM < 90600 + onlineAnalyzeMemoryContext = + AllocSetContextCreate(CacheMemoryContext, + "online_analyze storage context", + ALLOCSET_DEFAULT_MINSIZE, + ALLOCSET_DEFAULT_INITSIZE, + ALLOCSET_DEFAULT_MAXSIZE + ); +#else + onlineAnalyzeMemoryContext = + AllocSetContextCreate(CacheMemoryContext, + "online_analyze storage context", ALLOCSET_DEFAULT_SIZES); +#endif + } + + hash_ctl.hcxt = onlineAnalyzeMemoryContext; + flags |= HASH_CONTEXT; + + hash_ctl.keysize = sizeof(Oid); + + hash_ctl.entrysize = sizeof(OnlineAnalyzeTableStat); + flags |= HASH_ELEM; + + relstats = hash_create("online_analyze storage", 1024, &hash_ctl, flags); +} + +void _PG_init(void); +void +_PG_init(void) +{ + relstatsInit(); + + oldExecutorEndHook = ExecutorEnd_hook; + + ExecutorEnd_hook = onlineAnalyzeHooker; + +#if PG_VERSION_NUM >= 90200 + oldProcessUtilityHook = ProcessUtility_hook; + + ProcessUtility_hook = onlineAnalyzeHookerUtility; +#endif + + + DefineCustomBoolVariable( + "online_analyze.enable", + "Enable on-line analyze", + "Enables analyze of table directly after insert/update/delete/select into", + &online_analyze_enable, +#if PG_VERSION_NUM >= 80400 + online_analyze_enable, +#endif + PGC_USERSET, +#if PG_VERSION_NUM >= 80400 + GUC_NOT_IN_SAMPLE, +#if PG_VERSION_NUM >= 90100 + NULL, +#endif +#endif + NULL, + NULL + ); + + DefineCustomBoolVariable( + "online_analyze.local_tracking", + "Per backend tracking", + "Per backend tracking for temp tables (do not use system statistic)", + &online_analyze_local_tracking, +#if PG_VERSION_NUM >= 80400 + online_analyze_local_tracking, +#endif + PGC_USERSET, +#if PG_VERSION_NUM >= 80400 + GUC_NOT_IN_SAMPLE, +#if PG_VERSION_NUM >= 90100 + NULL, +#endif +#endif + NULL, + NULL + ); + + DefineCustomBoolVariable( + "online_analyze.verbose", + "Verbosity of on-line analyze", + "Make ANALYZE VERBOSE after table's changes", + &online_analyze_verbose, +#if PG_VERSION_NUM >= 80400 + online_analyze_verbose, +#endif + PGC_USERSET, +#if PG_VERSION_NUM >= 80400 + GUC_NOT_IN_SAMPLE, +#if PG_VERSION_NUM >= 90100 + NULL, +#endif +#endif + NULL, + NULL + ); + + DefineCustomRealVariable( + "online_analyze.scale_factor", + "fraction of table size to start on-line analyze", + "fraction of table size to start on-line analyze", + &online_analyze_scale_factor, +#if PG_VERSION_NUM >= 80400 + online_analyze_scale_factor, +#endif + 0.0, + 1.0, + PGC_USERSET, +#if PG_VERSION_NUM >= 80400 + GUC_NOT_IN_SAMPLE, +#if PG_VERSION_NUM >= 90100 + NULL, +#endif +#endif + NULL, + NULL + ); + + DefineCustomIntVariable( + "online_analyze.threshold", + "min number of row updates before on-line analyze", + "min number of row updates before on-line analyze", + &online_analyze_threshold, +#if PG_VERSION_NUM >= 80400 + online_analyze_threshold, +#endif + 0, + 0x7fffffff, + PGC_USERSET, +#if PG_VERSION_NUM >= 80400 + GUC_NOT_IN_SAMPLE, +#if PG_VERSION_NUM >= 90100 + NULL, +#endif +#endif + NULL, + NULL + ); + + DefineCustomIntVariable( + "online_analyze.capacity_threshold", + "Max local cache table capacity", + "Max local cache table capacity", + &online_analyze_capacity_threshold, +#if PG_VERSION_NUM >= 80400 + online_analyze_capacity_threshold, +#endif + 0, + 0x7fffffff, + PGC_USERSET, +#if PG_VERSION_NUM >= 80400 + GUC_NOT_IN_SAMPLE, +#if PG_VERSION_NUM >= 90100 + NULL, +#endif +#endif + NULL, + NULL + ); + + DefineCustomRealVariable( + "online_analyze.min_interval", + "minimum time interval between analyze call (in milliseconds)", + "minimum time interval between analyze call (in milliseconds)", + &online_analyze_min_interval, +#if PG_VERSION_NUM >= 80400 + online_analyze_min_interval, +#endif + 0.0, + 1e30, + PGC_USERSET, +#if PG_VERSION_NUM >= 80400 + GUC_NOT_IN_SAMPLE, +#if PG_VERSION_NUM >= 90100 + NULL, +#endif +#endif + NULL, + NULL + ); + + DefineCustomEnumVariable( + "online_analyze.table_type", + "Type(s) of table for online analyze: all(default), persistent, temporary, none", + NULL, + &online_analyze_table_type, +#if PG_VERSION_NUM >= 80400 + online_analyze_table_type, +#endif + online_analyze_table_type_options, + PGC_USERSET, +#if PG_VERSION_NUM >= 80400 + GUC_NOT_IN_SAMPLE, +#if PG_VERSION_NUM >= 90100 + NULL, +#endif +#endif + NULL, + NULL + ); + + DefineCustomStringVariable( + "online_analyze.exclude_tables", + "List of tables which will not online analyze", + NULL, + &excludeTables.tableStr, +#if PG_VERSION_NUM >= 80400 + "", +#endif + PGC_USERSET, + 0, +#if PG_VERSION_NUM >= 90100 + excludeTablesCheck, + excludeTablesAssign, +#else + excludeTablesAssign, +#endif + excludeTablesShow + ); + + DefineCustomStringVariable( + "online_analyze.include_tables", + "List of tables which will online analyze", + NULL, + &includeTables.tableStr, +#if PG_VERSION_NUM >= 80400 + "", +#endif + PGC_USERSET, + 0, +#if PG_VERSION_NUM >= 90100 + includeTablesCheck, + includeTablesAssign, +#else + includeTablesAssign, +#endif + includeTablesShow + ); + + DefineCustomIntVariable( + "online_analyze.lower_limit", + "min number of rows in table to analyze", + "min number of rows in table to analyze", + &online_analyze_lower_limit, +#if PG_VERSION_NUM >= 80400 + online_analyze_lower_limit, +#endif + 0, + 0x7fffffff, + PGC_USERSET, +#if PG_VERSION_NUM >= 80400 + GUC_NOT_IN_SAMPLE, +#if PG_VERSION_NUM >= 90100 + NULL, +#endif +#endif + NULL, + NULL + ); + + RegisterXactCallback(removeTable, NULL); +} + +void _PG_fini(void); +void +_PG_fini(void) +{ + ExecutorEnd_hook = oldExecutorEndHook; +#if PG_VERSION_NUM >= 90200 + ProcessUtility_hook = oldProcessUtilityHook; +#endif + + if (excludeTables.tables) + free(excludeTables.tables); + if (includeTables.tables) + free(includeTables.tables); + + excludeTables.tables = includeTables.tables = NULL; + excludeTables.nTables = includeTables.nTables = 0; +} diff --git a/contrib/plantuner/COPYRIGHT b/contrib/plantuner/COPYRIGHT new file mode 100644 index 00000000000..6e4705bc561 --- /dev/null +++ b/contrib/plantuner/COPYRIGHT @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2009 Teodor Sigaev <teodor@sigaev.ru> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY CONTRIBUTORS ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + diff --git a/contrib/plantuner/Makefile b/contrib/plantuner/Makefile new file mode 100644 index 00000000000..f2e8350e84c --- /dev/null +++ b/contrib/plantuner/Makefile @@ -0,0 +1,15 @@ +MODULE_big = plantuner +DOCS = README.plantuner +REGRESS = plantuner +OBJS=plantuner.o + +ifdef USE_PGXS +PGXS = $(shell pg_config --pgxs) +include $(PGXS) +else +subdir = contrib/plantuner +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global + +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/plantuner/README.plantuner b/contrib/plantuner/README.plantuner new file mode 100644 index 00000000000..17c8ba010b8 --- /dev/null +++ b/contrib/plantuner/README.plantuner @@ -0,0 +1,99 @@ +Plantuner - enable planner hints + + contrib/plantuner is a contribution module for PostgreSQL 8.4+, which + enable planner hints. + + All work was done by Teodor Sigaev (teodor@sigaev.ru) and Oleg Bartunov + (oleg@sai.msu.su). + + Sponsor: Nomao project (http://www.nomao.com) + +Motivation + + Whether somebody think it's bad or not, but sometime it's very + interesting to be able to control planner (provide hints, which tells + optimizer to ignore its algorithm in part), which is currently + impossible in POstgreSQL. Oracle, for example, has over 120 hints, SQL + Server also provides hints. + + This first version of plantuner provides a possibility to hide + specified indexes from PostgreSQL planner, so it will not use them. + + There are many situation, when developer want to temporarily disable + specific index(es), without dropping them, or to instruct planner to + use specific index. + + Next, for some workload PostgreSQL could be too pessimistic for + newly created tables and assumes much more rows in table than + it actually has. If plantuner.fix_empty_table GUC variable is set + to true then module will set to zero number of pages/tuples of + table which hasn't blocks in file. + +Installation + + * Get latest source of plantuner from CVS Repository + * gmake && gmake install && gmake installcheck + +Syntax + plantuner.forbid_index (deprecated) + plantuner.disable_index + List of indexes invisible to planner + plantuner.enable_index + List of indexes visible to planner even they are hided + by plantuner.disable_index. + plantuner.only_index + List of explicitly enabled indexes (overload plantuner.disable_index + and plantuner.enable_index), so, only indexes in this list are allowed. + +Usage + + To enable the module you can either load shared library 'plantuner' in + psql session or specify 'shared_preload_libraries' option in + postgresql.conf. +=# LOAD 'plantuner'; +=# create table test(id int); +=# create index id_idx on test(id); +=# create index id_idx2 on test(id); +=# \d test + Table "public.test" + Column | Type | Modifiers +--------+---------+----------- + id | integer | +Indexes: + "id_idx" btree (id) + "id_idx2" btree (id) +=# explain select id from test where id=1; + QUERY PLAN +----------------------------------------------------------------------- + Bitmap Heap Scan on test (cost=4.34..15.03 rows=12 width=4) + Recheck Cond: (id = 1) + -> Bitmap Index Scan on id_idx2 (cost=0.00..4.34 rows=12 width=0) + Index Cond: (id = 1) +(4 rows) +=# set enable_seqscan=off; +=# set plantuner.disable_index='id_idx2'; +=# explain select id from test where id=1; + QUERY PLAN +---------------------------------------------------------------------- + Bitmap Heap Scan on test (cost=4.34..15.03 rows=12 width=4) + Recheck Cond: (id = 1) + -> Bitmap Index Scan on id_idx (cost=0.00..4.34 rows=12 width=0) + Index Cond: (id = 1) +(4 rows) +=# set plantuner.disable_index='id_idx2,id_idx'; +=# explain select id from test where id=1; + QUERY PLAN +------------------------------------------------------------------------- + Seq Scan on test (cost=10000000000.00..10000000040.00 rows=12 width=4) + Filter: (id = 1) +(2 rows) +=# set plantuner.enable_index='id_idx'; +=# explain select id from test where id=1; + QUERY PLAN +----------------------------------------------------------------------- + Bitmap Heap Scan on test (cost=4.34..15.03 rows=12 width=4) + Recheck Cond: (id = 1) + -> Bitmap Index Scan on id_idx (cost=0.00..4.34 rows=12 width=0) + Index Cond: (id = 1) +(4 rows) + diff --git a/contrib/plantuner/expected/plantuner.out b/contrib/plantuner/expected/plantuner.out new file mode 100644 index 00000000000..70d2bcaaef2 --- /dev/null +++ b/contrib/plantuner/expected/plantuner.out @@ -0,0 +1,96 @@ +LOAD 'plantuner'; +SHOW plantuner.disable_index; + plantuner.disable_index +------------------------- + +(1 row) + +CREATE TABLE wow (i int, j int); +CREATE INDEX i_idx ON wow (i); +CREATE INDEX j_idx ON wow (j); +CREATE INDEX i1 ON WOW (i); +CREATE INDEX i2 ON WOW (i); +CREATE INDEX i3 ON WOW (i); +SET enable_seqscan=off; +SELECT * FROM wow; + i | j +---+--- +(0 rows) + +SET plantuner.disable_index="i_idx, j_idx"; +SELECT * FROM wow; + i | j +---+--- +(0 rows) + +SHOW plantuner.disable_index; + plantuner.disable_index +---------------------------- + public.i_idx, public.j_idx +(1 row) + +SET plantuner.disable_index="i_idx, nonexistent, public.j_idx, wow"; +WARNING: 'nonexistent' does not exist +WARNING: 'wow' is not an index +SHOW plantuner.disable_index; + plantuner.disable_index +---------------------------- + public.i_idx, public.j_idx +(1 row) + +SET plantuner.enable_index="i_idx"; +SHOW plantuner.enable_index; + plantuner.enable_index +------------------------ + public.i_idx +(1 row) + +SELECT * FROM wow; + i | j +---+--- +(0 rows) + +--test only index +RESET plantuner.disable_index; +RESET plantuner.enable_index; +SET enable_seqscan=off; +SET enable_bitmapscan=off; +SET enable_indexonlyscan=off; +SET plantuner.only_index="i1"; +SHOW plantuner.only_index; + plantuner.only_index +---------------------- + public.i1 +(1 row) + +EXPLAIN (COSTS OFF) SELECT * FROM wow WHERE i = 0; + QUERY PLAN +---------------------------- + Index Scan using i1 on wow + Index Cond: (i = 0) +(2 rows) + +SET plantuner.disable_index="i1,i2,i3"; +EXPLAIN (COSTS OFF) SELECT * FROM wow WHERE i = 0; + QUERY PLAN +---------------------------- + Index Scan using i1 on wow + Index Cond: (i = 0) +(2 rows) + +SET plantuner.only_index="i2"; +EXPLAIN (COSTS OFF) SELECT * FROM wow WHERE i = 0; + QUERY PLAN +---------------------------- + Index Scan using i2 on wow + Index Cond: (i = 0) +(2 rows) + +RESET plantuner.only_index; +EXPLAIN (COSTS OFF) SELECT * FROM wow WHERE i = 0; + QUERY PLAN +------------------------------- + Index Scan using i_idx on wow + Index Cond: (i = 0) +(2 rows) + diff --git a/contrib/plantuner/plantuner.c b/contrib/plantuner/plantuner.c new file mode 100644 index 00000000000..f87dcea21ff --- /dev/null +++ b/contrib/plantuner/plantuner.c @@ -0,0 +1,554 @@ +/* + * Copyright (c) 2009 Teodor Sigaev <teodor@sigaev.ru> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY CONTRIBUTORS ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <postgres.h> + +#include <fmgr.h> +#include <miscadmin.h> +#include <access/heapam.h> +#include <access/xact.h> +#include <catalog/namespace.h> +#include <catalog/pg_class.h> +#include <nodes/pg_list.h> +#include <optimizer/plancat.h> +#include <storage/bufmgr.h> +#include <utils/builtins.h> +#include <utils/guc.h> +#include <utils/lsyscache.h> +#include <utils/rel.h> +#if PG_VERSION_NUM >= 100000 +#include <utils/regproc.h> +#include <utils/varlena.h> +#endif + +PG_MODULE_MAGIC; + +#if PG_VERSION_NUM >= 130000 +#define heap_open(r, l) table_open(r, l) +#define heap_close(r, l) table_close(r, l) +#endif + +static int nDisabledIndexes = 0; +static Oid *disabledIndexes = NULL; +static char *disableIndexesOutStr = ""; + +static int nEnabledIndexes = 0; +static Oid *enabledIndexes = NULL; +static char *enableIndexesOutStr = ""; + +static int nOnlyIndexes = 0; +static Oid *onlyIndexes = NULL; +static char *onlyIndexesOutStr = ""; + +get_relation_info_hook_type prevHook = NULL; +static bool fix_empty_table = false; + +static bool plantuner_enable_inited = false; +static bool plantuner_only_inited = false; +static bool plantuner_disable_inited = false; + +typedef enum IndexListKind { + EnabledKind, + DisabledKind, + OnlyKind +} IndexListKind; + +static const char * +indexesAssign(const char * newval, bool doit, GucSource source, + IndexListKind kind) +{ + char *rawname; + List *namelist; + ListCell *l; + Oid *newOids = NULL; + int nOids = 0, + i = 0; + + rawname = pstrdup(newval); + + if (!SplitIdentifierString(rawname, ',', &namelist)) + goto cleanup; + + /* + * follow work could be done only in normal processing because of + * accsess to system catalog + */ + if (MyBackendId == InvalidBackendId || !IsUnderPostmaster || + !IsTransactionState()) + { + /* reset init state */ + switch(kind) + { + case EnabledKind: + plantuner_enable_inited = false; + break; + case DisabledKind: + plantuner_disable_inited = false; + break; + case OnlyKind: + plantuner_only_inited = false; + break; + default: + elog(ERROR, "wrong kind"); + } + + return newval; + } + + if (doit) + { + nOids = list_length(namelist); + newOids = malloc(sizeof(Oid) * (nOids+1)); + if (!newOids) + elog(ERROR,"could not allocate %d bytes", + (int)(sizeof(Oid) * (nOids+1))); + } + + switch(kind) + { + case EnabledKind: + plantuner_enable_inited = true; + break; + case DisabledKind: + plantuner_disable_inited = true; + break; + case OnlyKind: + plantuner_only_inited = true; + break; + default: + elog(ERROR, "wrong kind"); + } + + foreach(l, namelist) + { + char *curname = (char *) lfirst(l); +#if PG_VERSION_NUM >= 90200 + Oid indexOid = RangeVarGetRelid( + makeRangeVarFromNameList(stringToQualifiedNameList(curname)), + NoLock, true); +#else + Oid indexOid = RangeVarGetRelid( + makeRangeVarFromNameList(stringToQualifiedNameList(curname)), + true); +#endif + + if (indexOid == InvalidOid) + { +#if PG_VERSION_NUM >= 90100 + if (doit == false) +#endif + elog(WARNING,"'%s' does not exist", curname); + continue; + } + else if ( get_rel_relkind(indexOid) != RELKIND_INDEX ) + { +#if PG_VERSION_NUM >= 90100 + if (doit == false) +#endif + elog(WARNING,"'%s' is not an index", curname); + continue; + } + else if (doit) + { + newOids[i++] = indexOid; + } + } + + if (doit) + { + switch(kind) + { + case EnabledKind: + nEnabledIndexes = i; + if (enabledIndexes) + free(enabledIndexes); + enabledIndexes = newOids; + break; + case DisabledKind: + nDisabledIndexes = i; + if (disabledIndexes) + free(disabledIndexes); + disabledIndexes = newOids; + break; + case OnlyKind: + nOnlyIndexes = i; + if (onlyIndexes) + free(onlyIndexes); + onlyIndexes = newOids; + break; + default: + elog(ERROR, "wrong kind"); + } + } + + pfree(rawname); + list_free(namelist); + + return newval; + +cleanup: + if (newOids) + free(newOids); + pfree(rawname); + list_free(namelist); + return NULL; +} + +static const char * +assignDisabledIndexes(const char * newval, bool doit, GucSource source) +{ + return indexesAssign(newval, doit, source, DisabledKind); +} + +static const char * +assignEnabledIndexes(const char * newval, bool doit, GucSource source) +{ + return indexesAssign(newval, doit, source, EnabledKind); +} + +static const char * +assignOnlyIndexes(const char * newval, bool doit, GucSource source) +{ + return indexesAssign(newval, doit, source, OnlyKind); +} + +static void +lateInit() +{ + if (!plantuner_only_inited) + indexesAssign(onlyIndexesOutStr, true, PGC_S_USER, OnlyKind); + if (!plantuner_enable_inited) + indexesAssign(enableIndexesOutStr, true, PGC_S_USER, EnabledKind); + if (!plantuner_disable_inited) + indexesAssign(disableIndexesOutStr, true, PGC_S_USER, DisabledKind); +} + +#if PG_VERSION_NUM >= 90100 + +static bool +checkOnlyIndexes(char **newval, void **extra, GucSource source) +{ + char *val; + + val = (char*)indexesAssign(*newval, false, source, OnlyKind); + + if (val) + { + *newval = val; + return true; + } + + return false; +} + +static bool +checkDisabledIndexes(char **newval, void **extra, GucSource source) +{ + char *val; + + val = (char*)indexesAssign(*newval, false, source, DisabledKind); + + if (val) + { + *newval = val; + return true; + } + + return false; +} + +static bool +checkEnabledIndexes(char **newval, void **extra, GucSource source) +{ + char *val; + + val = (char*)indexesAssign(*newval, false, source, EnabledKind); + + if (val) + { + *newval = val; + return true; + } + + return false; +} + +static void +assignDisabledIndexesNew(const char *newval, void *extra) +{ + assignDisabledIndexes(newval, true, PGC_S_USER /* doesn't matter */); +} + +static void +assignEnabledIndexesNew(const char *newval, void *extra) +{ + assignEnabledIndexes(newval, true, PGC_S_USER /* doesn't matter */); +} + +static void +assignOnlyIndexesNew(const char *newval, void *extra) +{ + assignOnlyIndexes(newval, true, PGC_S_USER /* doesn't matter */); +} + +#endif + +static void +indexFilter(PlannerInfo *root, Oid relationObjectId, bool inhparent, + RelOptInfo *rel) +{ + int i; + + lateInit(); + + if (nOnlyIndexes > 0) + { + ListCell *l; + +restart1: + foreach(l, rel->indexlist) + { + IndexOptInfo *info = (IndexOptInfo*)lfirst(l); + bool remove = true; + + for(i=0; remove && i<nOnlyIndexes; i++) + if (onlyIndexes[i] == info->indexoid) + remove = false; + + if (remove) + { + rel->indexlist = list_delete_ptr(rel->indexlist, info); + goto restart1; + } + } + + return; + } + + for(i=0; i<nDisabledIndexes; i++) + { + ListCell *l; + + foreach(l, rel->indexlist) + { + IndexOptInfo *info = (IndexOptInfo*)lfirst(l); + + if (disabledIndexes[i] == info->indexoid) + { + int j; + + for(j=0; j<nEnabledIndexes; j++) + if (enabledIndexes[j] == info->indexoid) + break; + + if (j >= nEnabledIndexes) + rel->indexlist = list_delete_ptr(rel->indexlist, info); + + break; + } + } + } +} + +static void +execPlantuner(PlannerInfo *root, Oid relationObjectId, bool inhparent, + RelOptInfo *rel) +{ + Relation relation; + + relation = heap_open(relationObjectId, NoLock); + if (relation->rd_rel->relkind == RELKIND_RELATION) + { + if (fix_empty_table && RelationGetNumberOfBlocks(relation) == 0) + { + /* + * estimate_rel_size() could be too pessimistic for particular + * workload + */ + rel->pages = 1.0; + rel->tuples = 0.0; + } + + indexFilter(root, relationObjectId, inhparent, rel); + } + heap_close(relation, NoLock); + + /* + * Call next hook if it exists + */ + if (prevHook) + prevHook(root, relationObjectId, inhparent, rel); +} + +static const char* +IndexFilterShow(Oid* indexes, int nIndexes) +{ + char *val, *ptr; + int i, + len; + + lateInit(); + + len = 1 /* \0 */ + nIndexes * (2 * NAMEDATALEN + 2 /* ', ' */ + 1 /* . */); + ptr = val = palloc(len); + + *ptr =(char)'\0'; + for(i=0; i<nIndexes; i++) + { + char *relname = get_rel_name(indexes[i]); + Oid nspOid = get_rel_namespace(indexes[i]); + char *nspname = get_namespace_name(nspOid); + + if ( relname == NULL || nspOid == InvalidOid || nspname == NULL ) + continue; + + ptr += snprintf(ptr, len - (ptr - val), "%s%s.%s", + (i==0) ? "" : ", ", + nspname, + relname); + } + + return val; +} + +static const char* +disabledIndexFilterShow(void) +{ + return IndexFilterShow(disabledIndexes, nDisabledIndexes); +} + +static const char* +enabledIndexFilterShow(void) +{ + return IndexFilterShow(enabledIndexes, nEnabledIndexes); +} + +static const char* +onlyIndexFilterShow(void) +{ + return IndexFilterShow(onlyIndexes, nOnlyIndexes); +} + +void _PG_init(void); +void +_PG_init(void) +{ + DefineCustomStringVariable( + "plantuner.forbid_index", + "List of forbidden indexes (deprecated)", + "Listed indexes will not be used in queries (deprecated, use plantuner.disable_index)", + &disableIndexesOutStr, + "", + PGC_USERSET, + 0, +#if PG_VERSION_NUM >= 90100 + checkDisabledIndexes, + assignDisabledIndexesNew, +#else + assignDisabledIndexes, +#endif + disabledIndexFilterShow + ); + + DefineCustomStringVariable( + "plantuner.disable_index", + "List of disabled indexes", + "Listed indexes will not be used in queries", + &disableIndexesOutStr, + "", + PGC_USERSET, + 0, +#if PG_VERSION_NUM >= 90100 + checkDisabledIndexes, + assignDisabledIndexesNew, +#else + assignDisabledIndexes, +#endif + disabledIndexFilterShow + ); + + DefineCustomStringVariable( + "plantuner.enable_index", + "List of enabled indexes (overload plantuner.disable_index)", + "Listed indexes which could be used in queries even they are listed in plantuner.disable_index", + &enableIndexesOutStr, + "", + PGC_USERSET, + 0, +#if PG_VERSION_NUM >= 90100 + checkEnabledIndexes, + assignEnabledIndexesNew, +#else + assignEnabledIndexes, +#endif + enabledIndexFilterShow + ); + + DefineCustomStringVariable( + "plantuner.only_index", + "List of explicitly enabled indexes (overload plantuner.disable_index and plantuner.enable_index)", + "Only indexes in this list are allowed", + &onlyIndexesOutStr, + "", + PGC_USERSET, + 0, +#if PG_VERSION_NUM >= 90100 + checkOnlyIndexes, + assignOnlyIndexesNew, +#else + assignOnlyIndexes, +#endif + onlyIndexFilterShow + ); + + DefineCustomBoolVariable( + "plantuner.fix_empty_table", + "Sets to zero estimations for empty tables", + "Sets to zero estimations for empty or newly created tables", + &fix_empty_table, +#if PG_VERSION_NUM >= 80400 + fix_empty_table, +#endif + PGC_USERSET, +#if PG_VERSION_NUM >= 80400 + GUC_NOT_IN_SAMPLE, +#if PG_VERSION_NUM >= 90100 + NULL, +#endif +#endif + NULL, + NULL + ); + + if (get_relation_info_hook != execPlantuner ) + { + prevHook = get_relation_info_hook; + get_relation_info_hook = execPlantuner; + } +} diff --git a/contrib/plantuner/sql/plantuner.sql b/contrib/plantuner/sql/plantuner.sql new file mode 100644 index 00000000000..ddd6fcc94f1 --- /dev/null +++ b/contrib/plantuner/sql/plantuner.sql @@ -0,0 +1,51 @@ +LOAD 'plantuner'; + +SHOW plantuner.disable_index; + +CREATE TABLE wow (i int, j int); +CREATE INDEX i_idx ON wow (i); +CREATE INDEX j_idx ON wow (j); +CREATE INDEX i1 ON WOW (i); +CREATE INDEX i2 ON WOW (i); +CREATE INDEX i3 ON WOW (i); + +SET enable_seqscan=off; + +SELECT * FROM wow; + +SET plantuner.disable_index="i_idx, j_idx"; + +SELECT * FROM wow; + +SHOW plantuner.disable_index; + +SET plantuner.disable_index="i_idx, nonexistent, public.j_idx, wow"; + +SHOW plantuner.disable_index; + +SET plantuner.enable_index="i_idx"; + +SHOW plantuner.enable_index; + +SELECT * FROM wow; +--test only index +RESET plantuner.disable_index; +RESET plantuner.enable_index; + +SET enable_seqscan=off; +SET enable_bitmapscan=off; +SET enable_indexonlyscan=off; + +SET plantuner.only_index="i1"; +SHOW plantuner.only_index; + +EXPLAIN (COSTS OFF) SELECT * FROM wow WHERE i = 0; + +SET plantuner.disable_index="i1,i2,i3"; +EXPLAIN (COSTS OFF) SELECT * FROM wow WHERE i = 0; + +SET plantuner.only_index="i2"; +EXPLAIN (COSTS OFF) SELECT * FROM wow WHERE i = 0; + +RESET plantuner.only_index; +EXPLAIN (COSTS OFF) SELECT * FROM wow WHERE i = 0; diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index 93ae45d85ed..4676465cdd3 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -2423,32 +2423,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 @@ -2457,7 +2460,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; @@ -2866,16 +2869,13 @@ select c2 * (random() <= 1)::int as c2 from ft2 group by c2 * (random() <= 1)::i -- GROUP BY clause in various forms, cardinal, alias and constant expression explain (verbose, costs off) select count(c2) w, c2 x, 5 y, 7.0 z from ft1 group by 2, y, 9.0::int order by 2; - QUERY PLAN ---------------------------------------------------------------------------------------- - Sort + QUERY PLAN +------------------------------------------------------------------------------------------------------------ + Foreign Scan Output: (count(c2)), c2, 5, 7.0, 9 - Sort Key: ft1.c2 - -> Foreign Scan - Output: (count(c2)), c2, 5, 7.0, 9 - Relations: Aggregate on (public.ft1) - Remote SQL: SELECT count(c2), c2, 5, 7.0, 9 FROM "S 1"."T 1" GROUP BY 2, 3, 5 -(7 rows) + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT count(c2), c2, 5, 7.0, 9 FROM "S 1"."T 1" GROUP BY 2, 3, 5 ORDER BY c2 ASC NULLS LAST +(4 rows) select count(c2) w, c2 x, 5 y, 7.0 z from ft1 group by 2, y, 9.0::int order by 2; w | x | y | z diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c index b0ece666293..7bf68c54325 100644 --- a/src/backend/access/heap/hio.c +++ b/src/backend/access/heap/hio.c @@ -589,7 +589,8 @@ loop: else if (!ConditionalLockRelationForExtension(relation, ExclusiveLock)) { /* Couldn't get the lock immediately; wait for it. */ - LockRelationForExtension(relation, ExclusiveLock); + LockRelationForExtension(relation, ShareLock); + UnlockRelationForExtension(relation, ShareLock); /* * Check if some other backend has extended a block for us while @@ -601,13 +602,20 @@ loop: * If some other waiter has already extended the relation, we * don't need to do so; just use the existing freespace. */ + if (targetBlock != InvalidBlockNumber) + goto loop; + + /* Time to bulk-extend. */ + LockRelationForExtension(relation, ExclusiveLock); + + /* last chance */ + targetBlock = GetPageWithFreeSpace(relation, targetFreeSpace); if (targetBlock != InvalidBlockNumber) { UnlockRelationForExtension(relation, ExclusiveLock); goto loop; } - /* Time to bulk-extend. */ RelationAddExtraBlocks(relation, bistate); } } diff --git a/src/backend/access/heap/visibilitymap.c b/src/backend/access/heap/visibilitymap.c index e09f25a684c..8b98ab2907e 100644 --- a/src/backend/access/heap/visibilitymap.c +++ b/src/backend/access/heap/visibilitymap.c @@ -668,6 +668,7 @@ vm_extend(Relation rel, BlockNumber vm_nblocks) * to keep checking for creation or extension of the file, which happens * infrequently. */ + if (!RELATION_IS_LOCAL(rel)) CacheInvalidateSmgr(reln->smgr_rnode); UnlockRelationForExtension(rel, ExclusiveLock); diff --git a/src/backend/access/transam/twophase.c b/src/backend/access/transam/twophase.c index 5293c6920b3..ca7037eb2f9 100644 --- a/src/backend/access/transam/twophase.c +++ b/src/backend/access/transam/twophase.c @@ -486,7 +486,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 85e2a902a2c..27450e7b397 100644 --- a/src/backend/catalog/dependency.c +++ b/src/backend/catalog/dependency.c @@ -323,6 +323,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 db697a9fc38..83933a41937 100644 --- a/src/backend/catalog/index.c +++ b/src/backend/catalog/index.c @@ -60,6 +60,7 @@ #include "commands/tablecmds.h" #include "commands/tablespace.h" #include "commands/trigger.h" +#include "commands/typecmds.h" #include "executor/executor.h" #include "miscadmin.h" #include "nodes/makefuncs.h" @@ -109,7 +110,7 @@ static TupleDesc ConstructTupleDescriptor(Relation heapRelation, List *indexColNames, Oid accessMethodObjectId, Oid *collationObjectId, - Oid *classObjectId); + Oid *classObjectId); static void InitializeAttributeOids(Relation indexRelation, int numatts, Oid indexoid); static void AppendAttributeTuples(Relation indexRelation, Datum *attopts); @@ -136,7 +137,7 @@ static void SetReindexProcessing(Oid heapOid, Oid indexOid); static void ResetReindexProcessing(void); static void SetReindexPending(List *indexes); static void RemoveReindexPending(Oid indexOid); - +static void IndexTypeCreate(Relation indexRelation); /* * relationHasPrimaryKey @@ -650,6 +651,112 @@ UpdateIndexRelation(Oid indexoid, heap_freetuple(tuple); } +/* + * We only need to create reltype for multicolumn user-defined + * B-tree indexes that don't have a reltype yet. + */ +#define INDEX_NEEDS_RELTYPE(indexRelation, indexInfo, accessMethodOid) ( \ + !IsSystemRelation(indexRelation) \ + && indexInfo->ii_NumIndexKeyAttrs > 1 \ + && accessMethodOid == BTREE_AM_OID \ + && indexRelation->rd_rel->reltype == InvalidOid \ + && (!IsBinaryUpgrade || binary_upgrade_next_pg_type_oid != InvalidOid)) + +/* + * IndexTypeCreate + * + * Create type for specified index. + */ +void +IndexTypeCreate(Relation indexRelation) +{ + Oid ownerId = GetUserId(); + Oid namespaceId = RelationGetNamespace(indexRelation); + Oid new_array_oid = AssignTypeArrayOid(); + ObjectAddress new_type_addr; + char* relarrayname; + + /* Index must not have a reltype yet */ + Assert(indexRelation->rd_rel->reltype == InvalidOid); + + /* + * Build compound type for compound index to be able to use it in statistic. + * We need to collect statistic for compound indexes to be able to better + * predict selectivity of multicolumn joins. + */ + new_type_addr = TypeCreate(InvalidOid, + RelationGetRelationName(indexRelation), + namespaceId, + RelationGetRelid(indexRelation), + RELKIND_INDEX, + ownerId, /* owner's ID */ + -1, /* internal size (varlena) */ + TYPTYPE_COMPOSITE, /* type-type (composite) */ + TYPCATEGORY_COMPOSITE, /* type-category (ditto) */ + false, /* composite types are never preferred */ + DEFAULT_TYPDELIM, /* default array delimiter */ + F_RECORD_IN, /* input procedure */ + F_RECORD_OUT, /* output procedure */ + F_RECORD_RECV, /* receive procedure */ + F_RECORD_SEND, /* send procedure */ + InvalidOid, /* typmodin procedure - none */ + InvalidOid, /* typmodout procedure - none */ + InvalidOid, /* analyze procedure - default */ + InvalidOid, /* subscript procedure - default */ + InvalidOid, /* array element type - irrelevant */ + false, /* this is not an array type */ + new_array_oid, /* array type if any */ + InvalidOid, /* domain base type - irrelevant */ + NULL, /* default value - none */ + NULL, /* default binary representation */ + false, /* passed by reference */ + 'd', /* alignment - must be the largest! */ + 'x', /* fully TOASTable */ + -1, /* typmod */ + 0, /* array dimensions for typBaseType */ + false, /* Type NOT NULL */ + InvalidOid); /* rowtypes never have a collation */ + + indexRelation->rd_rel->reltype = new_type_addr.objectId; + + relarrayname = makeArrayTypeName(RelationGetRelationName(indexRelation), + namespaceId); + + TypeCreate(new_array_oid, /* force the type's OID to this */ + relarrayname, /* Array type name */ + namespaceId, /* Same namespace as parent */ + InvalidOid, /* Not composite, no relationOid */ + 0, /* relkind, also N/A here */ + ownerId, /* owner's ID */ + -1, /* Internal size (varlena) */ + TYPTYPE_BASE, /* Not composite - typelem is */ + TYPCATEGORY_ARRAY, /* type-category (array) */ + false, /* array types are never preferred */ + DEFAULT_TYPDELIM, /* default array delimiter */ + F_ARRAY_IN, /* array input proc */ + F_ARRAY_OUT, /* array output proc */ + F_ARRAY_RECV, /* array recv (bin) proc */ + F_ARRAY_SEND, /* array send (bin) proc */ + InvalidOid, /* typmodin procedure - none */ + InvalidOid, /* typmodout procedure - none */ + F_ARRAY_TYPANALYZE, /* array analyze procedure */ + F_ARRAY_SUBSCRIPT_HANDLER, /* subscript procedure - default */ + indexRelation->rd_rel->reltype, /* array element type - the rowtype */ + true, /* yes, this is an array type */ + InvalidOid, /* this has no array type */ + InvalidOid, /* domain base type - irrelevant */ + NULL, /* default value - none */ + NULL, /* default binary representation */ + false, /* passed by reference */ + 'd', /* alignment - must be the largest! */ + 'x', /* fully TOASTable */ + -1, /* typmod */ + 0, /* array dimensions for typBaseType */ + false, /* Type NOT NULL */ + InvalidOid); /* rowtypes never have a collation */ + + pfree(relarrayname); +} /* * index_create @@ -733,6 +840,7 @@ index_create(Relation heapRelation, bool invalid = (flags & INDEX_CREATE_INVALID) != 0; bool concurrent = (flags & INDEX_CREATE_CONCURRENT) != 0; bool partitioned = (flags & INDEX_CREATE_PARTITIONED) != 0; + bool withoutType = (flags & INDEX_CREATE_WITHOUT_TYPE) != 0; char relkind; TransactionId relfrozenxid; MultiXactId relminmxid; @@ -967,6 +1075,11 @@ index_create(Relation heapRelation, Assert(relminmxid == InvalidMultiXactId); Assert(indexRelationId == RelationGetRelid(indexRelation)); + /* Create a reltype for index if it is needed */ + if (withoutType == false && INDEX_NEEDS_RELTYPE(indexRelation, indexInfo, accessMethodObjectId) + && !is_internal) + IndexTypeCreate(indexRelation); + /* * Obtain exclusive lock on it. Although no other transactions can see it * until we commit, this prevents deadlock-risk complaints from lock @@ -1418,7 +1531,7 @@ index_concurrently_create_copy(Relation heapRelation, Oid oldIndexId, indclass->values, indcoloptions->values, optionDatum, - INDEX_CREATE_SKIP_BUILD | INDEX_CREATE_CONCURRENT, + INDEX_CREATE_SKIP_BUILD | INDEX_CREATE_CONCURRENT | INDEX_CREATE_WITHOUT_TYPE, 0, true, /* allow table to be a system catalog? */ false, /* is_internal? */ @@ -1559,6 +1672,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); @@ -1747,8 +1886,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); @@ -2137,6 +2277,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; @@ -2213,24 +2354,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 */ @@ -2330,6 +2453,16 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode) /* ensure that stats are dropped if transaction commits */ pgstat_drop_relation(userIndexRelation); + /* + * We might have stored multicolumn statistics for btree indexes. They are + * created only for non-system and non-TOAST indexes, so check only for such + * such indexes. + */ + remove_statistics = + IndexRelationGetNumberOfKeyAttributes(userIndexRelation) > 1 && + userIndexRelation->rd_rel->relam == BTREE_AM_OID && + !IsSystemRelation(userIndexRelation); + /* * Close and flush the index's relcache entry, to ensure relcache doesn't * try to rebuild it while we're deleting catalog entries. We keep the @@ -2357,10 +2490,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); /* @@ -2889,6 +3022,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); @@ -3726,6 +3867,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 01efdd5b044..e38d811b2f8 100644 --- a/src/backend/commands/analyze.c +++ b/src/backend/commands/analyze.c @@ -41,8 +41,11 @@ #include "common/pg_prng.h" #include "executor/executor.h" #include "foreign/fdwapi.h" +#include "funcapi.h" #include "miscadmin.h" #include "nodes/nodeFuncs.h" +#include "nodes/makefuncs.h" +#include "nodes/pg_list.h" #include "parser/parse_oper.h" #include "parser/parse_relation.h" #include "pgstat.h" @@ -67,6 +70,7 @@ #include "utils/spccache.h" #include "utils/syscache.h" #include "utils/timestamp.h" +#include "utils/typcache.h" /* Per-index data for ANALYZE */ @@ -76,6 +80,7 @@ typedef struct AnlIndexData double tupleFract; /* fraction of rows for partial index */ VacAttrStats **vacattrstats; /* index attrs to analyze */ int attr_cnt; + bool multicolumn; /* Collect compound row statistic for multicolumn index */ } AnlIndexData; @@ -487,6 +492,21 @@ do_analyze_rel(Relation onerel, VacuumParams *params, } thisdata->attr_cnt = tcnt; } + else if (indexInfo->ii_NumIndexAttrs > 1 && va_cols == NIL && + Irel[ind]->rd_rel->reltype != InvalidOid) + { + /* Collect statistic for multicolumn index for better predicting selectivity of multicolumn joins */ + RowExpr* row = makeNode(RowExpr); + row->row_typeid = Irel[ind]->rd_rel->reltype; + row->row_format = COERCE_EXPLICIT_CAST; + row->location = -1; + row->colnames = NULL; + thisdata->vacattrstats = (VacAttrStats **)palloc(sizeof(VacAttrStats *)); + thisdata->vacattrstats[0] = examine_attribute(Irel[ind], 1, (Node*)row); + thisdata->vacattrstats[0]->tupDesc = lookup_type_cache(row->row_typeid, TYPECACHE_TUPDESC)->tupDesc; + thisdata->attr_cnt = 1; + thisdata->multicolumn = true; + } } } @@ -921,28 +941,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++; } } } @@ -2728,6 +2761,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 060c6186ddd..2567b447d0d 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -384,21 +384,15 @@ ExplainOneQuery(Query *query, int cursorOptions, else { PlannedStmt *plan; - instr_time planstart, - planduration; BufferUsage bufusage_start, bufusage; if (es->buffers) bufusage_start = pgBufferUsage; - INSTR_TIME_SET_CURRENT(planstart); /* plan the query */ plan = pg_plan_query(query, queryString, cursorOptions, params); - INSTR_TIME_SET_CURRENT(planduration); - INSTR_TIME_SUBTRACT(planduration, planstart); - /* calc differences of buffer counters. */ if (es->buffers) { @@ -408,7 +402,7 @@ ExplainOneQuery(Query *query, int cursorOptions, /* run it (if needed) and produce output */ ExplainOnePlan(plan, into, es, queryString, params, queryEnv, - &planduration, (es->buffers ? &bufusage : NULL)); + &plan->planDuration, (es->buffers ? &bufusage : NULL)); } } diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c index 26d7972f837..f9740226152 100644 --- a/src/backend/executor/execExpr.c +++ b/src/backend/executor/execExpr.c @@ -251,13 +251,33 @@ ExecInitQual(List *qual, PlanState *parent) foreach(lc, qual) { Expr *node = (Expr *) lfirst(lc); + ExprEvalStep *lastStep; /* first evaluate expression */ ExecInitExprRec(node, state, &state->resvalue, &state->resnull); /* then emit EEOP_QUAL to detect if it's false (or null) */ scratch.d.qualexpr.jumpdone = -1; + + lastStep = &state->steps[state->steps_len-1]; + if (list_length(qual) == 1 && + (lastStep->opcode == EEOP_BOOL_OR_STEP_LAST || + lastStep->opcode == EEOP_BOOL_AND_STEP_LAST)) + scratch.d.qualexpr.guaranteed_empty = + lastStep->d.boolexpr.guaranteed_empty; + else if (list_length(qual) == 1 && + lastStep->opcode == EEOP_SUBPLAN) + { + scratch.d.qualexpr.guaranteed_empty = + lastStep->d.subplan.guaranteed_empty = + palloc(sizeof(bool)); + *scratch.d.qualexpr.guaranteed_empty = false; + } + else + scratch.d.qualexpr.guaranteed_empty = NULL; + ExprEvalPushStep(state, &scratch); + adjust_jumps = lappend_int(adjust_jumps, state->steps_len - 1); } @@ -1307,8 +1327,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 @@ -1327,10 +1354,16 @@ ExecInitExprRec(Expr *node, ExprState *state, foreach(lc, boolexpr->args) { Expr *arg = (Expr *) lfirst(lc); + ExprEvalStep *lastStep; /* Evaluate argument into our output variable */ ExecInitExprRec(arg, state, resv, resnull); + lastStep = &state->steps[state->steps_len-1]; + if (lastStep->opcode == EEOP_SUBPLAN) + lastStep->d.subplan.guaranteed_empty = + scratch.d.boolexpr.guaranteed_empty; + /* Perform the appropriate step type */ switch (boolexpr->boolop) { @@ -1415,6 +1448,7 @@ ExecInitExprRec(Expr *node, ExprState *state, scratch.opcode = EEOP_SUBPLAN; scratch.d.subplan.sstate = sstate; + scratch.d.subplan.sstate->guaranteed_empty = NULL; ExprEvalPushStep(state, &scratch); break; @@ -3857,6 +3891,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 87c7603f2b8..240ec08f2c9 100644 --- a/src/backend/executor/execExprInterp.c +++ b/src/backend/executor/execExprInterp.c @@ -807,6 +807,9 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) EEO_JUMP(op->d.boolexpr.jumpdone); } + /* reset */ + *op->d.boolexpr.guaranteed_empty = false; + EEO_NEXT(); } @@ -815,6 +818,8 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) if (*op->resnull) { /* result is already set to NULL, need not change it */ + /* reset */ + *op->d.boolexpr.guaranteed_empty = false; } else if (!DatumGetBool(*op->resvalue)) { @@ -830,10 +835,15 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) { *op->resvalue = (Datum) 0; *op->resnull = true; + /* reset */ + *op->d.boolexpr.guaranteed_empty = false; } else { /* result is already set to TRUE, need not change it */ + /* reset */ + *op->d.boolexpr.guaranteed_empty = false; + } EEO_NEXT(); @@ -852,6 +862,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) EEO_CASE(EEOP_BOOL_OR_STEP_FIRST) { *op->d.boolexpr.anynull = false; + *op->d.boolexpr.count_guaranteed_empty = 0; /* * EEOP_BOOL_OR_STEP_FIRST resets anynull, otherwise it's the same @@ -863,6 +874,10 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) EEO_CASE(EEOP_BOOL_OR_STEP) { + *op->d.boolexpr.count_guaranteed_empty += + (int) (*op->d.boolexpr.guaranteed_empty); + *op->d.boolexpr.guaranteed_empty = false; + if (*op->resnull) { *op->d.boolexpr.anynull = true; @@ -879,6 +894,10 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) EEO_CASE(EEOP_BOOL_OR_STEP_LAST) { + *op->d.boolexpr.count_guaranteed_empty += + (int) (*op->d.boolexpr.guaranteed_empty); + *op->d.boolexpr.guaranteed_empty = false; + if (*op->resnull) { /* result is already set to NULL, need not change it */ @@ -900,6 +919,10 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) } else { + if (*op->d.boolexpr.count_guaranteed_empty == op->d.boolexpr.nargs) + *op->d.boolexpr.guaranteed_empty = true; + else + *op->d.boolexpr.guaranteed_empty = false; /* result is already set to FALSE, need not change it */ } @@ -930,6 +953,9 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) /* ... bail out early, returning FALSE */ *op->resnull = false; *op->resvalue = BoolGetDatum(false); + if (op->d.qualexpr.guaranteed_empty && + op - state->steps == state->steps_len - 2 /* + EEOP_DONE */) + state->guaranteed_empty = *op->d.qualexpr.guaranteed_empty; EEO_JUMP(op->d.qualexpr.jumpdone); } @@ -3968,7 +3994,16 @@ ExecEvalSubPlan(ExprState *state, ExprEvalStep *op, ExprContext *econtext) /* could potentially be nested, so make sure there's enough stack */ check_stack_depth(); - *op->resvalue = ExecSubPlan(sstate, econtext, op->resnull); + if (sstate->guaranteed_empty == false) + *op->resvalue = ExecSubPlan(sstate, econtext, op->resnull); + else + { + *op->resvalue = false; + *op->resnull = false; + } + + if (op->opcode == EEOP_SUBPLAN && op->d.subplan.guaranteed_empty && sstate->guaranteed_empty) + *op->d.subplan.guaranteed_empty = sstate->guaranteed_empty; } /* diff --git a/src/backend/executor/execScan.c b/src/backend/executor/execScan.c index 5ae8f4da72c..d373ec0afc4 100644 --- a/src/backend/executor/execScan.c +++ b/src/backend/executor/execScan.c @@ -186,6 +186,7 @@ ExecScan(ScanState *node, * storage allocated in the previous tuple cycle. */ ResetExprContext(econtext); + node->ps.guaranteed_empty = false; /* * get a tuple from the access method. Loop until we obtain a tuple that @@ -244,7 +245,14 @@ ExecScan(ScanState *node, return slot; } } - else + else if (qual && qual->guaranteed_empty) + { + /* Qual guarantees the absence of results */ + node->ps.guaranteed_empty = true; + ExecClearTuple(slot); + + return slot; + } else InstrCountFiltered1(node, 1); /* diff --git a/src/backend/executor/nodeMaterial.c b/src/backend/executor/nodeMaterial.c index 2cb27e0e9ae..284a9f27bad 100644 --- a/src/backend/executor/nodeMaterial.c +++ b/src/backend/executor/nodeMaterial.c @@ -135,6 +135,8 @@ ExecMaterial(PlanState *pstate) if (TupIsNull(outerslot)) { node->eof_underlying = true; + if (tuplestore_tuple_count(tuplestorestate) == 0) + node->ss.ps.guaranteed_empty = true; return NULL; } @@ -363,6 +365,9 @@ ExecReScanMaterial(MaterialState *node) */ if (outerPlan->chgParam == NULL) ExecReScan(outerPlan); + else + node->ss.ps.guaranteed_empty = false; + node->eof_underlying = false; } } diff --git a/src/backend/executor/nodeNestloop.c b/src/backend/executor/nodeNestloop.c index 06767c3133f..c5fc14a51bb 100644 --- a/src/backend/executor/nodeNestloop.c +++ b/src/backend/executor/nodeNestloop.c @@ -164,6 +164,11 @@ ExecNestLoop(PlanState *pstate) { ENL1_printf("no inner tuple, need new outer tuple"); + if (innerPlan->guaranteed_empty && + (node->js.jointype == JOIN_INNER || + node->js.jointype == JOIN_SEMI)) + return NULL; + node->nl_NeedNewOuter = true; if (!node->nl_MatchedOuter && diff --git a/src/backend/executor/nodeSubplan.c b/src/backend/executor/nodeSubplan.c index a33efce3fe1..afb0d728a8a 100644 --- a/src/backend/executor/nodeSubplan.c +++ b/src/backend/executor/nodeSubplan.c @@ -84,7 +84,11 @@ ExecSubPlan(SubPlanState *node, /* Select appropriate evaluation strategy */ if (subplan->useHashTable) + { retval = ExecHashSubPlan(node, econtext, isNull); + if (node->planstate->guaranteed_empty) + node->guaranteed_empty = true; + } else retval = ExecScanSubPlan(node, econtext, isNull); @@ -105,6 +109,9 @@ ExecHashSubPlan(SubPlanState *node, SubPlan *subplan = node->subplan; PlanState *planstate = node->planstate; TupleTableSlot *slot; + bool hasParam = (planstate->plan->extParam != NULL || + subplan->setParam != NIL || + planstate->chgParam != NULL); /* Shouldn't have any direct correlation Vars */ if (subplan->parParam != NIL || node->args != NIL) @@ -122,8 +129,11 @@ ExecHashSubPlan(SubPlanState *node, * lefthand side. */ *isNull = false; - if (!node->havehashrows && !node->havenullrows) + if (!node->havehashrows && !node->havenullrows) { + if (hasParam == false) + node->planstate->guaranteed_empty = true; return BoolGetDatum(false); + } /* * Evaluate lefthand expressions and form a projection tuple. First we diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c index a7080f5cb24..6642367ae0d 100644 --- a/src/backend/nodes/nodeFuncs.c +++ b/src/backend/nodes/nodeFuncs.c @@ -2637,16 +2637,26 @@ range_table_entry_walker(RangeTblEntry *rte, Node * expression_tree_mutator(Node *node, Node *(*mutator) (), - void *context) + void *context, + int flags) { /* * The mutator has already decided not to modify the current node, but we * must call the mutator for any sub-nodes. */ -#define FLATCOPY(newnode, node, nodetype) \ - ( (newnode) = (nodetype *) palloc(sizeof(nodetype)), \ - memcpy((newnode), (node), sizeof(nodetype)) ) +#define FLATCOPY(newnode, node, nodetype, flags) \ + do { \ + if ((flags) & QTW_DONT_COPY_DEFAULT) \ + { \ + (newnode) = (node); \ + } \ + else \ + { \ + (newnode) = (nodetype *) palloc(sizeof(nodetype)); \ + memcpy((newnode), (node), sizeof(nodetype)); \ + } \ + } while(0) #define MUTATE(newfield, oldfield, fieldtype) \ ( (newfield) = (fieldtype) mutator((Node *) (oldfield), context) ) @@ -2669,7 +2679,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; @@ -2678,7 +2688,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; } @@ -2699,7 +2709,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; } @@ -2708,7 +2718,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 *); @@ -2724,7 +2734,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 *); /* @@ -2747,7 +2757,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; @@ -2758,7 +2768,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, @@ -2776,7 +2786,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; } @@ -2786,7 +2796,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; } @@ -2796,7 +2806,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; } @@ -2806,7 +2816,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; } @@ -2816,7 +2826,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; } @@ -2826,7 +2836,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; } @@ -2836,7 +2846,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; } @@ -2846,7 +2856,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 *); /* @@ -2862,7 +2872,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) */ @@ -2876,7 +2886,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; } @@ -2886,7 +2896,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; } @@ -2896,7 +2906,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); @@ -2908,7 +2918,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; } @@ -2918,7 +2928,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; } @@ -2928,7 +2938,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; @@ -2939,7 +2949,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; } @@ -2949,7 +2959,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; } @@ -2959,7 +2969,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 *); @@ -2971,7 +2981,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; @@ -2982,7 +2992,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; } @@ -2992,7 +3002,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; @@ -3003,7 +3013,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; @@ -3014,7 +3024,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; } @@ -3024,7 +3034,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; } @@ -3034,7 +3044,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 *); @@ -3046,7 +3056,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; } @@ -3056,7 +3066,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; } @@ -3066,7 +3076,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; } @@ -3076,7 +3086,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; } @@ -3089,7 +3099,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 *); @@ -3102,7 +3112,7 @@ expression_tree_mutator(Node *node, CTECycleClause *cc = (CTECycleClause *) node; CTECycleClause *newnode; - FLATCOPY(newnode, cc, CTECycleClause); + FLATCOPY(newnode, cc, CTECycleClause, flags); MUTATE(newnode->cycle_mark_value, cc->cycle_mark_value, Node *); MUTATE(newnode->cycle_mark_default, cc->cycle_mark_default, Node *); return (Node *) newnode; @@ -3113,7 +3123,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 @@ -3132,7 +3142,7 @@ expression_tree_mutator(Node *node, PartitionBoundSpec *pbs = (PartitionBoundSpec *) node; PartitionBoundSpec *newnode; - FLATCOPY(newnode, pbs, PartitionBoundSpec); + FLATCOPY(newnode, pbs, PartitionBoundSpec, flags); MUTATE(newnode->listdatums, pbs->listdatums, List *); MUTATE(newnode->lowerdatums, pbs->lowerdatums, List *); MUTATE(newnode->upperdatums, pbs->upperdatums, List *); @@ -3144,7 +3154,7 @@ expression_tree_mutator(Node *node, PartitionRangeDatum *prd = (PartitionRangeDatum *) node; PartitionRangeDatum *newnode; - FLATCOPY(newnode, prd, PartitionRangeDatum); + FLATCOPY(newnode, prd, PartitionRangeDatum, flags); MUTATE(newnode->value, prd->value, Node *); return (Node *) newnode; } @@ -3174,7 +3184,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; @@ -3185,7 +3195,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 *); @@ -3200,7 +3210,7 @@ expression_tree_mutator(Node *node, MergeAction *action = (MergeAction *) node; MergeAction *newnode; - FLATCOPY(newnode, action, MergeAction); + FLATCOPY(newnode, action, MergeAction, flags); MUTATE(newnode->qual, action->qual, Node *); MUTATE(newnode->targetList, action->targetList, List *); @@ -3212,7 +3222,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; @@ -3226,7 +3236,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 *); @@ -3239,7 +3249,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 */ @@ -3251,7 +3261,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; @@ -3262,7 +3272,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; @@ -3273,7 +3283,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; } @@ -3283,7 +3293,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; @@ -3294,7 +3304,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; @@ -3305,7 +3315,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; @@ -3316,7 +3326,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; @@ -3327,7 +3337,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 *); @@ -3377,7 +3387,7 @@ query_tree_mutator(Query *query, { Query *newquery; - FLATCOPY(newquery, query, Query); + FLATCOPY(newquery, query, Query, flags); query = newquery; } @@ -3420,7 +3430,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 *); @@ -3469,7 +3479,7 @@ range_table_mutator(List *rtable, RangeTblEntry *rte = (RangeTblEntry *) lfirst(rt); RangeTblEntry *newrte; - FLATCOPY(newrte, rte, RangeTblEntry); + FLATCOPY(newrte, rte, RangeTblEntry, flags); switch (rte->rtekind) { case RTE_RELATION: diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 3f8e58626cc..c5a1344255e 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -1936,6 +1936,7 @@ _outAppendPath(StringInfo str, const AppendPath *node) WRITE_NODE_FIELD(subpaths); WRITE_INT_FIELD(first_partial_path); + WRITE_BOOL_FIELD(pull_tlist); WRITE_FLOAT_FIELD(limit_tuples, "%.0f"); } diff --git a/src/backend/optimizer/path/Makefile b/src/backend/optimizer/path/Makefile index 1e199ff66f7..06dd07f3270 100644 --- a/src/backend/optimizer/path/Makefile +++ b/src/backend/optimizer/path/Makefile @@ -14,6 +14,7 @@ include $(top_builddir)/src/Makefile.global OBJS = \ allpaths.o \ + appendorpath.o \ clausesel.o \ costsize.o \ equivclass.o \ diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 4e02439ce36..70cc18898d7 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -804,6 +804,9 @@ set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) /* Consider index scans */ create_index_paths(root, rel); + /* Consider index scans with rewrited quals */ + keybased_rewrite_index_paths(root, rel); + /* Consider TID scans */ create_tidscan_paths(root, rel); } @@ -1475,7 +1478,7 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel, if (subpaths_valid) add_path(rel, (Path *) create_append_path(root, rel, subpaths, NIL, NIL, NULL, 0, false, - -1)); + -1, false)); /* * Consider an append of unordered, unparameterized partial paths. Make @@ -1518,7 +1521,7 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel, appendpath = create_append_path(root, rel, NIL, partial_subpaths, NIL, NULL, parallel_workers, enable_parallel_append, - -1); + -1, false); /* * Make sure any subsequent partial paths use the same row count @@ -1567,7 +1570,7 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel, appendpath = create_append_path(root, rel, pa_nonpartial_subpaths, pa_partial_subpaths, NIL, NULL, parallel_workers, true, - partial_rows); + partial_rows, false); add_partial_path(rel, (Path *) appendpath); } @@ -1628,7 +1631,7 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel, add_path(rel, (Path *) create_append_path(root, rel, subpaths, NIL, NIL, required_outer, 0, false, - -1)); + -1, false)); } /* @@ -1655,7 +1658,7 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel, appendpath = create_append_path(root, rel, NIL, list_make1(path), NIL, NULL, path->parallel_workers, true, - partial_rows); + partial_rows, false); add_partial_path(rel, (Path *) appendpath); } } @@ -1907,7 +1910,7 @@ generate_orderedappend_paths(PlannerInfo *root, RelOptInfo *rel, NULL, 0, false, - -1)); + -1, false)); if (startup_neq_total) add_path(rel, (Path *) create_append_path(root, rel, @@ -1917,7 +1920,7 @@ generate_orderedappend_paths(PlannerInfo *root, RelOptInfo *rel, NULL, 0, false, - -1)); + -1, false)); if (fractional_subpaths) add_path(rel, (Path *) create_append_path(root, @@ -1928,7 +1931,7 @@ generate_orderedappend_paths(PlannerInfo *root, RelOptInfo *rel, NULL, 0, false, - -1)); + -1, false)); } else { @@ -2144,7 +2147,7 @@ set_dummy_rel_pathlist(RelOptInfo *rel) /* Set up the dummy path */ add_path(rel, (Path *) create_append_path(NULL, rel, NIL, NIL, NIL, rel->lateral_relids, - 0, false, -1)); + 0, false, -1, false)); /* * We set the cheapest-path fields immediately, just in case they were diff --git a/src/backend/optimizer/path/appendorpath.c b/src/backend/optimizer/path/appendorpath.c new file mode 100644 index 00000000000..b15fa2bc946 --- /dev/null +++ b/src/backend/optimizer/path/appendorpath.c @@ -0,0 +1,1061 @@ +/* + * support Append plan for ORed clauses + * Teodor Sigaev <teodor@sigaev.ru> + */ +#include "postgres.h" + +#include "access/skey.h" +#include "catalog/pg_am.h" +#include "optimizer/cost.h" +#include "optimizer/clauses.h" +#include "optimizer/optimizer.h" +#include "optimizer/paths.h" +#include "optimizer/pathnode.h" +#include "optimizer/planmain.h" +#include "optimizer/restrictinfo.h" +#include "utils/lsyscache.h" + +typedef struct CKey { + RestrictInfo *rinfo; /* original rinfo */ + int n; /* IndexPath's number in bitmapquals */ + OpExpr *normalizedexpr; /* expression with Var on left */ + Var *var; + Node *value; + Oid opfamily; + int strategy; + uint8 strategyMask; +} CKey; +#define BTMASK(x) ( 1<<(x) ) + +static List* find_common_quals( BitmapOrPath *path ); +static RestrictInfo* unionOperation(PlannerInfo *root, CKey *key); +static BitmapOrPath* cleanup_nested_quals( PlannerInfo *root, RelOptInfo *rel, BitmapOrPath *path ); +static List* sortIndexScans( List* ipaths ); +static List* reverseScanDirIdxPaths(List *indexPaths); +static IndexPath* reverseScanDirIdxPath(IndexPath *ipath); +static bool checkSameIndex(Path *path, Oid *indexoid); + +#define IS_LESS(a) ( (a) == BTLessStrategyNumber || (a)== BTLessEqualStrategyNumber ) +#define IS_GREATER(a) ( (a) == BTGreaterStrategyNumber || (a) == BTGreaterEqualStrategyNumber ) +#define IS_ONE_DIRECTION(a,b) ( \ + ( IS_LESS(a) && IS_LESS(b) ) \ + || \ + ( IS_GREATER(a) && IS_GREATER(b) ) \ +) + +typedef struct ExExpr { + OpExpr *expr; + Oid opfamily; + Oid lefttype; + Oid righttype; + int strategy; + int attno; +} ExExpr; + + +typedef struct IndexPathEx { + IndexPath *path; + List *preparedquals; /* list of ExExpr */ +} IndexPathEx; + +static List* +clauses_get_exprs(List *listIndexClause) { + ListCell *i, *c; + List *exprs=NULL; + + foreach(i, listIndexClause) + { + IndexClause *ic = lfirst(i); + + foreach(c, ic->indexquals) + { + RestrictInfo *rinfo = lfirst(c); + OpExpr *expr = (OpExpr*)rinfo->clause; + + exprs = lappend(exprs, expr); + } + } + + return exprs; +} + + +/*---------- + * keybased_rewrite_or_index_quals + * Examine join OR-of-AND quals to see if any useful common restriction + * clauses can be extracted. If so, try to use for creating new index paths. + * + * For example consider + * WHERE ( a.x=5 and a.y>10 ) OR a.x>5 + * and there is an index on a.x or (a.x, a.y). So, plan + * will be seqscan or BitmapOr(IndexPath,IndexPath) + * So, we can add some restriction: + * WHERE (( a.x=5 and a.y>10 ) OR a.x>5) AND a.x>=5 + * and plan may be so + * Index Scan (a.x>=5) + * Filter( (( a.x=5 and a.y>10 ) OR a.x>5) ) + * + * We don't want to add new clauses to baserestrictinfo, just + * use it as index quals. + * + * Next thing which it possible to test is use append of + * searches instead of OR. + * For example consider + * WHERE ( a.x=5 and a.y>10 ) OR a.x>6 + * and there is an index on (a.x) (a.x, a.y) + * So, we can suggest follow plan: + * Append + * Filter ( a.x=5 and a.y>10 ) OR (a.x>6) + * Index Scan (a.x=5) --in case of index on (a.x) + * Index Scan (a.x>6) + * For that we should proof that index quals isn't overlapped, + * also, some index quals may be containedi in other, so it can be eliminated + */ + +void +keybased_rewrite_index_paths(PlannerInfo *root, RelOptInfo *rel) +{ + BitmapOrPath *bestpath = NULL; + ListCell *i; + List *commonquals; + AppendPath *appendidxpath; + List *indexPaths; + IndexOptInfo *index; + + foreach(i, rel->baserestrictinfo) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(i); + + if (restriction_is_or_clause(rinfo) && + !rinfo->outerjoin_delayed) + { + /* + * Use the generate_bitmap_or_paths() machinery to estimate the + * value of each OR clause. We can use regular restriction + * clauses along with the OR clause contents to generate + * indexquals. We pass outer_rel = NULL so that sub-clauses + * that are actually joins will be ignored. + */ + List *orpaths; + ListCell *k; + + orpaths = generate_bitmap_or_paths(root, rel, + list_make1(rinfo), + rel->baserestrictinfo); + + /* Locate the cheapest OR path */ + foreach(k, orpaths) + { + BitmapOrPath *path = (BitmapOrPath *) lfirst(k); + Oid indexoid = InvalidOid; + + Assert(IsA(path, BitmapOrPath)); + + if (checkSameIndex((Path*)path, &indexoid) == false) + continue; + + if (bestpath == NULL || + path->path.total_cost < bestpath->path.total_cost) + { + bestpath = path; + } + } + } + } + + /* Fail if no suitable clauses found */ + if (bestpath == NULL) + return; + + commonquals = find_common_quals(bestpath); + /* Found quals with the same args, but with, may be, different + operations */ + if ( commonquals != NULL ) { + List *addon=NIL; + + foreach(i, commonquals) { + CKey *key = (CKey*)lfirst(i); + RestrictInfo *rinfo; + + /* + * get 'union' of operation for key + */ + rinfo = unionOperation(root, key); + if ( rinfo ) + addon = lappend(addon, rinfo); + } + + /* + * Ok, we found common quals and union it, so we will try to + * create new possible index paths + */ + if ( addon ) { + List *origbaserestrictinfo = list_copy(rel->baserestrictinfo); + + rel->baserestrictinfo = list_concat(rel->baserestrictinfo, addon); + + create_index_paths(root, rel); + + rel->baserestrictinfo = origbaserestrictinfo; + } + } + + /* + * Check if indexquals isn't overlapped and all index scan + * are on the same index. + */ + if ( (bestpath = cleanup_nested_quals( root, rel, bestpath )) == NULL ) + return; + + if (IsA(bestpath, IndexPath)) { + IndexPath *ipath = (IndexPath*)bestpath; + + /* + * It's possible to do only one index scan :) + */ + index = ipath->indexinfo; + + if ( root->query_pathkeys != NIL && index->sortopfamily && OidIsValid(index->sortopfamily[0]) ) + { + List *pathkeys; + + pathkeys = build_index_pathkeys(root, index, + ForwardScanDirection); + pathkeys = truncate_useless_pathkeys(root, rel, + pathkeys); + + ipath->path.pathkeys = pathkeys; + add_path(rel, (Path *) ipath); + + /* + * add path ordered in backward direction if our pathkeys + * is still unusable... + */ + if ( pathkeys == NULL || pathkeys_useful_for_ordering(root, pathkeys) == 0 ) + { + pathkeys = build_index_pathkeys(root, index, + BackwardScanDirection); + pathkeys = truncate_useless_pathkeys(root, rel, + pathkeys); + + ipath = reverseScanDirIdxPath( ipath ); + + ipath->path.pathkeys = pathkeys; + add_path(rel, (Path *) ipath); + } + } else + add_path(rel, (Path *) ipath); + return; + } + + /* recount costs */ + foreach(i, bestpath->bitmapquals ) { + IndexPath *ipath = (IndexPath*)lfirst(i); + + Assert( IsA(ipath, IndexPath) ); + ipath->path.rows = rel->tuples * clauselist_selectivity(root, + clauses_get_exprs(ipath->indexclauses), + rel->relid, + JOIN_INNER, + NULL); + ipath->path.rows = clamp_row_est(ipath->path.rows); + cost_index(ipath, root, 1, false); + } + + /* + * Check if append index can suggest ordering of result + * + * Also, we should say to AppendPath about targetlist: + * target list will be taked from indexscan + */ + index = ((IndexPath*)linitial(bestpath->bitmapquals))->indexinfo; + if ( root->query_pathkeys != NIL && index->sortopfamily && OidIsValid(index->sortopfamily[0]) && + (indexPaths = sortIndexScans( bestpath->bitmapquals )) !=NULL ) { + List *pathkeys; + + pathkeys = build_index_pathkeys(root, index, + ForwardScanDirection); + pathkeys = truncate_useless_pathkeys(root, rel, + pathkeys); + + appendidxpath = create_append_path(root, rel, indexPaths, NIL, pathkeys, NULL, 0, + 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, -1.0, + true); + add_path(rel, (Path *) appendidxpath); + } + } else { + appendidxpath = create_append_path(root, rel, bestpath->bitmapquals, + NIL, NIL, NULL, + 0, false, -1.0, true); + add_path(rel, (Path *) appendidxpath); + } +} + +/* + * returns true if all indexscan below uses the same index + */ +static bool +checkSameIndex(Path *path, Oid *indexoid) { + ListCell *i; + List *subpaths; + + if (IsA(path, IndexPath)) + { + IndexPath *indpath = (IndexPath*)path; + + if (indpath->indexinfo->relam != BTREE_AM_OID) + return false; + + if (*indexoid == InvalidOid) + *indexoid = indpath->indexinfo->indexoid; + else if (*indexoid != indpath->indexinfo->indexoid) + return false; + + return true; + } + else if (IsA(path, BitmapOrPath)) + { + BitmapOrPath *orpath = (BitmapOrPath*)path; + + subpaths = orpath->bitmapquals; + + } + else if (IsA(path, BitmapAndPath)) + { + BitmapAndPath *andpath = (BitmapAndPath*)path; + + subpaths = andpath->bitmapquals; + } + else + { + elog(ERROR, "unexpected path type: %d", nodeTag(path)); + } + + Assert(list_length(subpaths) > 0); + + foreach(i, subpaths) + { + Path *subpath = (Path *) lfirst(i); + + if (checkSameIndex(subpath, indexoid) == false) + return false; + } + + return true; +} + +/* + * transformToCkey - transform RestrictionInfo + * to CKey struct. Fucntion checks possibility and correctness of + * RestrictionInfo to use it as common key, normalizes + * expression and "caches" some information. Note, + * original RestrictInfo isn't touched + */ + +static CKey* +transformToCkey( IndexOptInfo *index, RestrictInfo* rinfo, int indexcol) { + CKey *key; + OpExpr *expr = (OpExpr*)rinfo->clause; + + if ( rinfo->outerjoin_delayed ) + return NULL; + + if ( !IsA(expr, OpExpr) ) + return NULL; + + if ( contain_mutable_functions((Node*)expr) ) + return NULL; + + if ( list_length( expr->args ) != 2 ) + return NULL; + + key = (CKey*)palloc(sizeof(CKey)); + key->rinfo = rinfo; + + key->normalizedexpr = (OpExpr*)copyObject( expr ); + if (!bms_equal(rinfo->left_relids, index->rel->relids)) + CommuteOpExpr(key->normalizedexpr); + + /* + * fix_indexqual_operand returns copy of object + */ + key->var = (Var*)fix_indexqual_operand(linitial(key->normalizedexpr->args), index, indexcol); + Assert( IsA(key->var, Var) ); + + key->opfamily = index->opfamily[ key->var->varattno - 1 ]; + + /* restore varattno, because it may be different in different index */ + key->var->varattno = key->var->varattnosyn; + + key->value = (Node*)lsecond(key->normalizedexpr->args); + + key->strategy = get_op_opfamily_strategy( key->normalizedexpr->opno, key->opfamily); + Assert( key->strategy != InvalidStrategy ); + + key->strategyMask = BTMASK(key->strategy); + + return key; +} + +/* + * get_index_quals - get list of quals in + * CKeys form + */ + +static List* +get_index_quals(IndexPath *path, int cnt) { + ListCell *i, *c; + List *quals = NIL; + + foreach(i, path->indexclauses) { + IndexClause *ic = lfirst(i); + + foreach(c, ic->indexquals) { + CKey *k = transformToCkey( path->indexinfo, + (RestrictInfo*)lfirst(c), + ic->indexcol); + if ( k ) { + k->n = cnt; + quals = lappend(quals, k); + } + } + } + return quals; +} + +/* + * extract all quals from bitmapquals->indexquals for + */ +static List* +find_all_quals( BitmapOrPath *path, int *counter ) { + ListCell *i,*j; + List *allquals = NIL; + + *counter = 0; + + foreach(i, path->bitmapquals ) + { + Path *subpath = (Path *) lfirst(i); + + if ( IsA(subpath, BitmapAndPath) ) { + foreach(j, ((BitmapAndPath*)subpath)->bitmapquals) { + Path *subsubpath = (Path *) lfirst(j); + + if ( IsA(subsubpath, IndexPath) ) { + if ( ((IndexPath*)subsubpath)->indexinfo->relam != BTREE_AM_OID ) + return NIL; + allquals = list_concat(allquals, get_index_quals( (IndexPath*)subsubpath, *counter )); + } else + return NIL; + } + } else if ( IsA(subpath, IndexPath) ) { + if ( ((IndexPath*)subpath)->indexinfo->relam != BTREE_AM_OID ) + return NIL; + allquals = list_concat(allquals, get_index_quals( (IndexPath*)subpath, *counter )); + } else + return NIL; + + (*counter)++; + } + + return allquals; +} + +/* + * Compares aruments of operation + */ +static bool +iseqCKeyArgs( CKey *a, CKey *b ) { + if ( a->opfamily != b->opfamily ) + return false; + + if ( !equal( a->value, b->value ) ) + return false; + + if ( !equal( a->var, b->var ) ) + return false; + + return true; +} + +/* + * Count entries of CKey with the same arguments + */ +static int +count_entry( List *allquals, CKey *tocmp ) { + ListCell *i; + int curcnt=0; + + foreach(i, allquals) { + CKey *key = lfirst(i); + + if ( key->n == curcnt ) { + continue; + } else if ( key->n == curcnt+1 ) { + if ( iseqCKeyArgs( key, tocmp ) ) { + tocmp->strategyMask |= key->strategyMask; + curcnt++; + } + } else + return -1; + } + + return curcnt+1; +} + +/* + * Finds all CKey with the same arguments + */ +static List* +find_common_quals( BitmapOrPath *path ) { + List *allquals; + List *commonquals = NIL; + ListCell *i; + int counter; + + if ( (allquals = find_all_quals( path, &counter ))==NIL ) + return NIL; + + foreach(i, allquals) { + CKey *key = lfirst(i); + + if ( key->n != 0 ) + break; + + if ( counter == count_entry(allquals, key) ) + commonquals = lappend( commonquals, key ); + } + + return commonquals; +} + +/* + * unionOperation - make RestrictInfo with combined operation + */ + +static RestrictInfo* +unionOperation(PlannerInfo *root, CKey *key) { + RestrictInfo *rinfo; + Oid lefttype, righttype; + int strategy; + + switch( key->strategyMask ) { + case BTMASK(BTLessStrategyNumber): + case BTMASK(BTLessEqualStrategyNumber): + case BTMASK(BTEqualStrategyNumber): + case BTMASK(BTGreaterEqualStrategyNumber): + case BTMASK(BTGreaterStrategyNumber): + /* trivial case */ + break; + case BTMASK(BTLessStrategyNumber) | BTMASK(BTLessEqualStrategyNumber): + case BTMASK(BTLessStrategyNumber) | BTMASK(BTLessEqualStrategyNumber) | BTMASK(BTEqualStrategyNumber): + case BTMASK(BTLessStrategyNumber) | BTMASK(BTEqualStrategyNumber): + case BTMASK(BTLessEqualStrategyNumber) | BTMASK(BTEqualStrategyNumber): + /* any subset of <, <=, = can be unioned with <= */ + key->strategy = BTLessEqualStrategyNumber; + break; + case BTMASK(BTGreaterEqualStrategyNumber) | BTMASK(BTGreaterStrategyNumber): + case BTMASK(BTEqualStrategyNumber) | BTMASK(BTGreaterEqualStrategyNumber) | BTMASK(BTGreaterStrategyNumber): + case BTMASK(BTEqualStrategyNumber) | BTMASK(BTGreaterStrategyNumber): + case BTMASK(BTEqualStrategyNumber) | BTMASK(BTGreaterEqualStrategyNumber): + /* any subset of >, >=, = can be unioned with >= */ + key->strategy = BTGreaterEqualStrategyNumber; + break; + default: + /* + * Can't make common restrict qual + */ + return NULL; + } + + get_op_opfamily_properties(key->normalizedexpr->opno, key->opfamily, false, + &strategy, &lefttype, &righttype); + + if ( strategy != key->strategy ) { + /* + * We should check because it's possible to have "strange" + * opfamilies - without some strategies... + */ + key->normalizedexpr->opno = get_opfamily_member(key->opfamily, lefttype, righttype, key->strategy); + + if ( key->normalizedexpr->opno == InvalidOid ) + return NULL; + + key->normalizedexpr->opfuncid = get_opcode( key->normalizedexpr->opno ); + Assert ( key->normalizedexpr->opfuncid != InvalidOid ); + } + + rinfo = make_simple_restrictinfo(root, (Expr*)key->normalizedexpr); + + return rinfo; +} + +/* + * Remove unneeded RestrioctionInfo nodes as it + * needed by predicate_*_by() + */ +static void +make_predicate(List *indexclauses, List **preds) { + ListCell *i, *c; + + *preds = NIL; + + foreach(i, indexclauses) + { + IndexClause *ic = lfirst(i); + RestrictInfo *rinfo = ic->rinfo; + + if ( rinfo->outerjoin_delayed ) + continue; + + foreach(c, ic->indexquals) + { + RestrictInfo *rinfoq = lfirst(c); + OpExpr *expr = (OpExpr*)rinfoq->clause; + + if ( !IsA(expr, OpExpr) ) + goto end; + + if ( list_length( expr->args ) != 2 ) + goto end; + } + + *preds = lappend(*preds, ic); + +end: + continue; + } +} + +#define CELL_GET_CLAUSES(x) ( ((IndexPath*)lfirst(x))->indexclauses ) + +/* + * returns list of all nested quals + */ +static List* +contained_quals(List *nested, List* quals, ListCell *check) { + ListCell *i; + List *checkpred; + + if ( list_member_ptr( nested, lfirst(check) ) ) + return nested; + + checkpred = clauses_get_exprs(CELL_GET_CLAUSES(check)); + + if ( contain_mutable_functions((Node*)checkpred) ) + return nested; + + foreach(i, quals ) + { + if ( check == i ) + continue; + + if ( list_member_ptr( nested, lfirst(i) ) ) + continue; + + if (predicate_implied_by( checkpred, + clauses_get_exprs(CELL_GET_CLAUSES(i)), + false ) ) + nested = lappend( nested, lfirst(i) ); + } + return nested; +} + +/* + * Checks that one row can be in several quals. + * It's guaranteed by predicate_refuted_by() + */ +static bool +is_intersect(List *quals, ListCell *check) { + ListCell *i; + List *checkpred=NULL; + + checkpred=clauses_get_exprs(CELL_GET_CLAUSES(check)); + Assert( checkpred != NULL ); + + for_each_cell(i, quals, check) { + if ( i==check ) + continue; + + if ( predicate_refuted_by( checkpred, + clauses_get_exprs(CELL_GET_CLAUSES(i)), + false ) == false ) + return true; + } + + return false; +} + +/* + * Removes nested quals and gurantees that quals are not intersected, + * ie one row can't satisfy to several quals. It's open a possibility of + * Append node using instead of BitmapOr + */ +static BitmapOrPath* +cleanup_nested_quals( PlannerInfo *root, RelOptInfo *rel, BitmapOrPath *path ) { + ListCell *i; + IndexOptInfo *index=NULL; + List *nested = NULL; + + /* + * check all path to use only one index + */ + foreach(i, path->bitmapquals ) + { + + if ( IsA(lfirst(i), IndexPath) ) { + List *preds; + IndexPath *subpath = (IndexPath *) lfirst(i); + + if ( subpath->indexinfo->relam != BTREE_AM_OID ) + return NULL; + + if ( index == NULL ) + index = subpath->indexinfo; + else if ( index->indexoid != subpath->indexinfo->indexoid ) + return NULL; + + /* + * work only with optimizable quals + */ + make_predicate(subpath->indexclauses, &preds); + if (preds == NIL) + return NULL; + subpath->indexclauses = preds; + } else + return NULL; + } + + /* + * eliminate nested quals + */ + foreach(i, path->bitmapquals ) { + nested = contained_quals(nested, path->bitmapquals, i); + } + + if ( nested != NIL ) { + path->bitmapquals = list_difference_ptr( path->bitmapquals, nested ); + + Assert( list_length( path->bitmapquals )>0 ); + + /* + * All quals becomes only one after eliminating nested quals + */ + if (list_length( path->bitmapquals ) == 1) + return (BitmapOrPath*)linitial(path->bitmapquals); + } + + /* + * Checks for intersection + */ + foreach(i, path->bitmapquals ) { + if ( is_intersect( path->bitmapquals, i ) ) + return NULL; + } + + return path; +} + +/* + * Checks if whole result of one simple operation is contained + * in another + */ +static int +simpleCmpExpr( ExExpr *a, ExExpr *b ) { + if ( predicate_implied_by((List*)a->expr, (List*)b->expr, false) ) + /* + * a:( Var < 15 ) > b:( Var <= 10 ) + */ + return 1; + else if ( predicate_implied_by((List*)b->expr, (List*)a->expr, false) ) + /* + * a:( Var <= 10 ) < b:( Var < 15 ) + */ + return -1; + else + return 0; +} + +/* + * Trys to define where is equation - on left or right side + * a(< 10) b(=11) - on right + * a(> 10) b(=9) - on left + * a(= 10) b(=11) - on right + * a(= 10) b(=9) - on left + * Any other - result is 0; + */ +static int +cmpEqExpr( ExExpr *a, ExExpr *b ) { + Oid oldop = b->expr->opno; + int res=0; + + b->expr->opno = get_opfamily_member(b->opfamily, b->lefttype, b->righttype, BTLessStrategyNumber); + if ( b->expr->opno != InvalidOid ) { + b->expr->opfuncid = get_opcode( b->expr->opno ); + res = simpleCmpExpr(a,b); + } + + if ( res == 0 ) { + b->expr->opno = get_opfamily_member(b->opfamily, b->lefttype, b->righttype, BTGreaterStrategyNumber); + if ( b->expr->opno != InvalidOid ) { + b->expr->opfuncid = get_opcode( b->expr->opno ); + res = -simpleCmpExpr(a,b); + } + } + + b->expr->opno = oldop; + b->expr->opfuncid = get_opcode( b->expr->opno ); + + return res; +} + +/* + * Is result of a contained in result of b or on the contrary? + */ +static int +cmpNegCmp( ExExpr *a, ExExpr *b ) { + Oid oldop = b->expr->opno; + int res = 0; + + b->expr->opno = get_negator( b->expr->opno ); + if ( b->expr->opno != InvalidOid ) { + b->expr->opfuncid = get_opcode( b->expr->opno ); + res = simpleCmpExpr(a,b); + } + + b->expr->opno = oldop; + b->expr->opfuncid = get_opcode( b->expr->opno ); + + return ( IS_LESS(a->strategy) ) ? res : -res; +} + +/* + * Returns 1 if whole result of a is on left comparing with result of b + * Returns -1 if whole result of a is on right comparing with result of b + * Return 0 if it's impossible to define or results is overlapped + * Expressions should use the same attribute of index and should be + * a simple: just one operation with index. + */ +static int +cmpExpr( ExExpr *a, ExExpr *b ) { + int res; + + /* + * If a and b are overlapped, we can't decide which one is + * lefter or righter + */ + if ( IS_ONE_DIRECTION(a->strategy, b->strategy) || + predicate_refuted_by((List*)a->expr, (List*)b->expr, false) == false ) + return 0; + + /* + * In this place it's impossible to have a row which satisfies + * a and b expressions, so we will try to find relatiove position of that results + */ + if (a->strategy == BTEqualStrategyNumber && + b->strategy == BTEqualStrategyNumber) { + return cmpEqExpr(a, b); + } else if ( b->strategy == BTEqualStrategyNumber ) { + return -cmpEqExpr(a, b); /* Covers cases with any operations in a */ + } else if ( a->strategy == BTEqualStrategyNumber ) { + return cmpEqExpr(b, a); + } else if ( (res = cmpNegCmp(a, b)) == 0 ) { /* so, a(<10) b(>20) */ + res = -cmpNegCmp(b, a); + } + + return res; +} + +static IndexOptInfo *sortingIndex = NULL; +static bool volatile unableToDefine = false; + +/* + * Try to define positions of result which satisfy indexquals a and b per + * one index's attribute. + */ +static int +cmpColumnQuals( List *a, List *b, int attno ) { + int res = 0; + ListCell *ai, *bi; + + foreach(ai, a) { + ExExpr *ae = (ExExpr*)lfirst(ai); + + if ( attno != ae->attno ) + continue; + + foreach(bi, b) { + ExExpr *be = (ExExpr*)lfirst(bi); + + if ( attno != be->attno ) + continue; + + if ((res=cmpExpr(ae, be))!=0) + return res; + + if (res == 0 && ae->strategy == be->strategy && + be->strategy != BTEqualStrategyNumber && + equal(ae->expr, be->expr)) + { + /* + * It's impossible to get defined order for non-eq the same clauses + */ + unableToDefine = true; + PG_RE_THROW(); /* it should be PG_THROW(), but it's the same */ + } + } + } + + return 0; +} + +/* + * Compare result of two indexquals. + * Warinig: it use PG_RE_THROW(), so any call should be wrapped with + * PG_TRY(). Try/catch construction is used here for minimize unneeded + * actions when sorting is impossible + */ +static int +cmpIndexPathEx(const void *a, const void *b) { + IndexPathEx *aipe = (IndexPathEx*)a; + IndexPathEx *bipe = (IndexPathEx*)b; + int attno, res = 0; + + for(attno=1; res==0 && attno<=sortingIndex->ncolumns; attno++) + res=cmpColumnQuals(aipe->preparedquals, bipe->preparedquals, attno); + + if ( res==0 ) { + unableToDefine = true; + PG_RE_THROW(); /* it should be PG_THROW(), but it's the same */ + } + + return res; +} + +/* + * Initialize lists of operation in useful form + */ +static List* +prepareQuals(IndexOptInfo *index, List *indexclauses) { + ListCell *i, *c; + List *res=NULL; + ExExpr *ex; + + foreach(i, indexclauses) + { + IndexClause *ic = lfirst(i); + RestrictInfo *rinfo = lfirst(i); + + if ( rinfo->outerjoin_delayed ) + return NULL; + + foreach(c, ic->indexquals) + { + RestrictInfo *rinfo = lfirst(c); + OpExpr *expr = (OpExpr*)rinfo->clause; + + if ( !IsA(expr, OpExpr) ) + return NULL; + + if ( list_length( expr->args ) != 2 ) + return NULL; + + if ( contain_mutable_functions((Node*)expr) ) + return NULL; + + ex = (ExExpr*)palloc(sizeof(ExExpr)); + ex->expr = (OpExpr*)copyObject( expr ); + if (!bms_equal(rinfo->left_relids, index->rel->relids)) + CommuteOpExpr(ex->expr); + linitial(ex->expr->args) = fix_indexqual_operand(linitial(ex->expr->args), index, ic->indexcol); + ex->attno = ((Var*)linitial(ex->expr->args))->varattno; + ex->opfamily = index->opfamily[ ex->attno - 1 ]; + get_op_opfamily_properties( ex->expr->opno, ex->opfamily, false, + &ex->strategy, &ex->lefttype, &ex->righttype); + + res = lappend(res, ex); + } + } + + return res; +} + +/* + * sortIndexScans - sorts index scans to get sorted results. + * Function supposed that index is the same for all + * index scans + */ +static List* +sortIndexScans( List* ipaths ) { + ListCell *i; + int j=0; + IndexPathEx *ipe = (IndexPathEx*)palloc( sizeof(IndexPathEx)*list_length(ipaths) ); + List *orderedPaths = NIL; + IndexOptInfo *index = ((IndexPath*)linitial(ipaths))->indexinfo; + + foreach(i, ipaths) { + ipe[j].path = (IndexPath*)lfirst(i); + ipe[j].preparedquals = prepareQuals(index, ipe[j].path->indexclauses); + + if (ipe[j].preparedquals == NULL) + return NULL; + j++; + } + + sortingIndex = index; + unableToDefine = false; + PG_TRY(); { + qsort(ipe, list_length(ipaths), sizeof(IndexPathEx), cmpIndexPathEx); + } PG_CATCH(); { + if ( unableToDefine == false ) + PG_RE_THROW(); /* not our problem */ + } PG_END_TRY(); + + if ( unableToDefine == true ) + return NULL; + + for(j=0;j<list_length(ipaths);j++) + orderedPaths = lappend(orderedPaths, ipe[j].path); + + return orderedPaths; +} + +static IndexPath* +reverseScanDirIdxPath(IndexPath *ipath) { + IndexPath *n = makeNode(IndexPath); + + *n = *ipath; + + n->indexscandir = BackwardScanDirection; + + return n; +} + +static List* +reverseScanDirIdxPaths(List *indexPaths) { + List *idxpath = NIL; + ListCell *i; + + foreach(i, indexPaths) { + idxpath = lcons(reverseScanDirIdxPath( (IndexPath*)lfirst(i) ), idxpath); + } + + return idxpath; +} diff --git a/src/backend/optimizer/path/clausesel.c b/src/backend/optimizer/path/clausesel.c index 06f836308d0..205fed125e5 100644 --- a/src/backend/optimizer/path/clausesel.c +++ b/src/backend/optimizer/path/clausesel.c @@ -12,8 +12,15 @@ * *------------------------------------------------------------------------- */ +#include <math.h> #include "postgres.h" +#include "access/genam.h" +#include "access/htup_details.h" +#include "catalog/pg_collation.h" +#include "common/pg_prng.h" +#include "commands/vacuum.h" +#include "funcapi.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" #include "optimizer/clauses.h" @@ -26,6 +33,16 @@ #include "utils/lsyscache.h" #include "utils/selfuncs.h" +#include "parser/parsetree.h" +#include "utils/array.h" +#include "utils/builtins.h" +#include "utils/rel.h" +#include "utils/syscache.h" +#include "utils/typcache.h" + +#define EXHAUSTIVE_IN_SELECTIVITY_THRESHOLD (default_statistics_target/4) +#define RANGE_IN_SELECTIVITY_THRESHOLD (default_statistics_target/20) + /* * Data structure for accumulating info about possible range-query * clause pairs in clauselist_selectivity. @@ -51,6 +68,1041 @@ static Selectivity clauselist_selectivity_or(PlannerInfo *root, SpecialJoinInfo *sjinfo, bool use_extended_stats); +static bool treat_as_join_clause(PlannerInfo *root, Node *clause, RestrictInfo *rinfo, + int varRelid, SpecialJoinInfo *sjinfo); + +typedef enum CorrelationKind { + CKRestrict = 0, + CKIndepend, /* unknown correlation */ + CKLikelySelf, /* Seems, should be close to be correlated, like agg with + self join */ + CKSelf, /* 100% correlation because of self join */ + CKMul /* product of all CKLikelySelf * CKSelf */ +} CorrelationKind; +static CorrelationKind get_correlation_kind(PlannerInfo *root, int varRelid, + OpExpr* expr); + +/* + * Get variabe node. Returns null if node is not a Var node. + */ +static inline Var* +get_var(Node* node) +{ + if (IsA(node, RelabelType)) + node = (Node *) ((RelabelType *) node)->arg; + + return IsA(node, Var) ? (Var*)node : NULL; +} + +/* + * Locate compound index which can be used for multicolumn clauses/join. + */ +static IndexOptInfo* +locate_inner_multicolumn_index(PlannerInfo *root, Index varno, List* vars, + int n_clauses, + int **permutation, List **missed_vars, int* n_keys) +{ + ListCell *ilist; + RelOptInfo *rel = find_base_rel(root, varno); + IndexOptInfo *index_opt = NULL; + List *missed_vars_opt = NIL; + int *permutation_opt = NULL; + int n_index_cols_opt = 0; + bool used[INDEX_MAX_KEYS]; + int posvars[INDEX_MAX_KEYS]; + + *n_keys = 0; + *missed_vars = NIL; + + Assert(list_length(vars) >= 1); + Assert(list_length(vars) <= n_clauses); + + foreach(ilist, rel->indexlist) + { + IndexOptInfo *index = (IndexOptInfo *) lfirst(ilist); + ListCell *vlist; + int i, n_index_cols = 0; + List *missed = NIL; + int *perm = NULL; + + 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 = pg_prng_uint64(&pg_global_prng_state) % iac->n_elems; + } + else if ((iac->curr_elem += carry) >= iac->n_elems) + { + iac->curr_elem = 0; + carry = 1; + } + else + carry = 0; + + it->values[j] = iac->elems[iac->curr_elem]; + it->isnull[j] = iac->nulls[iac->curr_elem]; + } + + return true; +} + +static double +get_numdistinct(PlannerInfo *root, IndexOptInfo* index, int n_keys) +{ + double numdistinct = 1.0; + ListCell *lc; + int i = 0; + + foreach(lc, index->indextlist) + { + TargetEntry *tle = lfirst(lc); + VariableStatData vardata; + bool isdefault; + + examine_variable(root, (Node*)tle->expr, 0, &vardata); + + numdistinct *= get_variable_numdistinct(&vardata, &isdefault); + + ReleaseVariableStats(vardata); + + if (++i >= n_keys) + break; + } + + if (numdistinct > index->tuples) + numdistinct = index->tuples; + + return numdistinct; +} + +static Selectivity +estimate_selectivity_by_index(PlannerInfo *root, IndexOptInfo* index, + VariableStatData *vardata, + List *consts, List** missed_vars, int *permutation, + List *in_clauses, int n_keys, + bool *usedEqSel) +{ + TupleIterator it; + Selectivity sum = 0.0; + TypeCacheEntry *typentry; + Datum constant; + int nBins; + double nDistinct = 0.0; + + if (n_keys < index->nkeycolumns ) + { + double nd; + bool isdefault; + + nDistinct = get_numdistinct(root, index, n_keys); + nd = get_variable_numdistinct(vardata, &isdefault); + + if (isdefault == false && nDistinct > nd) + nDistinct = sqrt(nDistinct * nd); + } + + /* + * Assume that two compound types are coherent, so we can use equality + * function from one type to compare it with other type. Use >= and <= range + * definition. + */ + typentry = lookup_type_cache(vardata->atttype, + TYPECACHE_EQ_OPR | TYPECACHE_TUPDESC); + initTupleIterator(&it, consts, permutation, in_clauses); + + /* + * Try to simplify calculations: if all variants matches to small amount of + * bins histogram the we don't need to check tuples separately, it's enough + * to checck min and max tuples and compute selecivity by range of bins + */ + + if (n_keys != index->nkeycolumns && + it.n_variants > RANGE_IN_SELECTIVITY_THRESHOLD) + { + Datum constantMax = 0, + constantMin = 0; + FmgrInfo opprocLT, opprocGT; + + fmgr_info(F_RECORD_GT, &opprocGT); + fmgr_info(F_RECORD_LT, &opprocLT); + + /* + * Find min and max tuples + */ + while(getTupleIterator(&it)) + { + /* we check cache invalidation message */ + if (typentry->tupDesc == NULL) + typentry = lookup_type_cache(vardata->atttype, + TYPECACHE_EQ_OPR | TYPECACHE_TUPDESC); + constant = HeapTupleGetDatum(heap_form_tuple(typentry->tupDesc, + it.values, it.isnull)); + + if (constantMax == 0 || + DatumGetBool(FunctionCall2Coll(&opprocGT, + DEFAULT_COLLATION_OID, + constant, constantMax))) + { + constantMax = constant; + if (constantMin != 0) + continue; + } + if (constantMin == 0 || + DatumGetBool(FunctionCall2Coll(&opprocLT, + DEFAULT_COLLATION_OID, + constant, constantMin))) + { + constantMin = constant; + } + } + + sum = prefix_record_histogram_selectivity(vardata, + constantMin, constantMax, + n_keys, nDistinct, + &nBins); + + if (sum > 0 && nBins <= it.n_variants) + /* + * 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 ****************************************************************************/ @@ -98,6 +1150,54 @@ static Selectivity clauselist_selectivity_or(PlannerInfo *root, * Of course this is all very dependent on the behavior of the inequality * selectivity functions; perhaps some day we can generalize the approach. */ + +static void +appendSelectivityRes(Selectivity s[5], Selectivity sel, CorrelationKind ck) +{ + switch(ck) + { + case CKRestrict: + s[ck] *= sel; + break; + case CKSelf: + case CKLikelySelf: + s[CKMul] *= sel; + if (s[ck] > sel) + s[ck] = sel; + /* FALLTHROUGH */ + case CKIndepend: + s[CKIndepend] *= sel; + break; + default: + elog(ERROR, "unknown selectivity kind: %d", ck); + } +} + +static Selectivity +finalizeSelectivityRes(Selectivity s[5]) +{ + Selectivity sel; + + sel = s[CKRestrict] * s[CKIndepend]; + + if (s[CKIndepend] != s[CKMul]) + { + /* we have both independ and correlated - fallback */ + sel *= s[CKMul]; + } + else + { + /* we have only correlated join clauses */ + if (s[CKLikelySelf] != 1.0 && sel < s[CKLikelySelf]) + sel = sel + (s[CKLikelySelf] - sel) * 0.25; + + if (s[CKSelf] != 1.0 && sel < s[CKSelf]) + sel = sel + (s[CKSelf] - sel) * 1.0; + } + + return sel; +} + Selectivity clauselist_selectivity(PlannerInfo *root, List *clauses, @@ -123,12 +1223,14 @@ clauselist_selectivity_ext(PlannerInfo *root, SpecialJoinInfo *sjinfo, bool use_extended_stats) { - Selectivity s1 = 1.0; + Selectivity s[5 /* per CorrelationKind */] = {1.0, 1.0, 1.0, 1.0, 1.0}; + Selectivity s2 = 1.0, s3 = 1.0; RelOptInfo *rel; Bitmapset *estimatedclauses = NULL; RangeQueryClause *rqlist = NULL; ListCell *l; int listidx; + CorrelationKind ck; /* * If there's exactly one clause, just go directly to @@ -152,9 +1254,23 @@ clauselist_selectivity_ext(PlannerInfo *root, * 'estimatedclauses' is populated with the 0-based list position * index of clauses estimated here, and that should be ignored below. */ - s1 = statext_clauselist_selectivity(root, clauses, varRelid, + s2 = statext_clauselist_selectivity(root, clauses, varRelid, jointype, sjinfo, rel, &estimatedclauses, false); + appendSelectivityRes(s, s2, CKRestrict); + } + + /* + * Check if join conjuncts corresponds to some compound indexes on left and + * right joined relations or indexed columns of one relation is compared + * with constant values. In this case selectivity of join can be calculated + * based on statistic of this compound index. + */ + while(use_multicolumn_statistic(root, clauses, varRelid, jointype, sjinfo, + &s2, &s3, &estimatedclauses, &ck)) + { + appendSelectivityRes(s, s2, CKRestrict); + appendSelectivityRes(s, s3, ck); } /* @@ -196,7 +1312,7 @@ clauselist_selectivity_ext(PlannerInfo *root, rinfo = (RestrictInfo *) clause; if (rinfo->pseudoconstant) { - s1 = s1 * s2; + appendSelectivityRes(s, s2, CKRestrict); continue; } clause = (Node *) rinfo->clause; @@ -210,12 +1326,17 @@ clauselist_selectivity_ext(PlannerInfo *root, * the simple way we are expecting.) Most of the tests here can be * done more efficiently with rinfo than without. */ + ck = treat_as_join_clause(root, clause, rinfo, varRelid, sjinfo) ? + CKIndepend : CKRestrict; if (is_opclause(clause) && list_length(((OpExpr *) clause)->args) == 2) { OpExpr *expr = (OpExpr *) clause; bool varonleft = true; bool ok; + if (ck == CKIndepend) + ck = get_correlation_kind(root, varRelid, expr); + if (rinfo) { ok = (bms_membership(rinfo->clause_relids) == BMS_SINGLETON) && @@ -254,7 +1375,7 @@ clauselist_selectivity_ext(PlannerInfo *root, break; default: /* Just merge the selectivity in generically */ - s1 = s1 * s2; + appendSelectivityRes(s, s2, ck); break; } continue; /* drop to loop bottom */ @@ -262,7 +1383,7 @@ clauselist_selectivity_ext(PlannerInfo *root, } /* Not the right form, so treat it generically. */ - s1 = s1 * s2; + appendSelectivityRes(s, s2, ck); } /* @@ -324,15 +1445,13 @@ clauselist_selectivity_ext(PlannerInfo *root, } } /* Merge in the selectivity of the pair of clauses */ - s1 *= s2; + appendSelectivityRes(s, s2, CKRestrict); } else { /* Only found one of a pair, merge it in generically */ - if (rqlist->have_lobound) - s1 *= rqlist->lobound; - else - s1 *= rqlist->hibound; + appendSelectivityRes(s, (rqlist->have_lobound) ? rqlist->lobound : + rqlist->hibound, CKRestrict); } /* release storage and advance */ rqnext = rqlist->next; @@ -340,7 +1459,7 @@ clauselist_selectivity_ext(PlannerInfo *root, rqlist = rqnext; } - return s1; + return finalizeSelectivityRes(s); } /* @@ -647,6 +1766,137 @@ treat_as_join_clause(PlannerInfo *root, Node *clause, RestrictInfo *rinfo, } } +typedef struct RangeTblEntryContext { + RangeTblEntry *rte; + int count; +} RangeTblEntryContext; + +static bool +find_rte_walker(Node *node, RangeTblEntryContext *context) +{ + if (node == NULL) + return false; + + if (context->count > 1) + return true; /* skip rest */ + + if (IsA(node, RangeTblEntry)) { + RangeTblEntry *rte = (RangeTblEntry*)node; + + if (rte->rtekind == RTE_RELATION) + { + if (context->count == 0) + { + context->count++; + context->rte=rte; + } + else if (rte->relid != context->rte->relid) + { + context->count++; + return true; /* more that one relation in subtree */ + } + } + else if (!(rte->rtekind == RTE_SUBQUERY || rte->rtekind == RTE_JOIN || + rte->rtekind == RTE_CTE)) + { + context->count++; + return true; /* more that one relation in subtree */ + } + + return false; /* allow range_table_walker to continue */ + } + + if (IsA(node, Query)) + return query_tree_walker((Query *) node, find_rte_walker, + (void *) context, QTW_EXAMINE_RTES_BEFORE); + + return expression_tree_walker(node, find_rte_walker, (void *) context); +} + +static RangeTblEntry* +find_single_rte(RangeTblEntry *node) +{ + RangeTblEntryContext context; + + context.rte = NULL; + context.count = 0; + + (void)range_table_walker(list_make1(node), + find_rte_walker, + (void *) &context, QTW_EXAMINE_RTES_BEFORE); + + return context.count == 1 ? context.rte : NULL; +} + +#define IsSameRelationRTE(a, b) ( \ + (a)->rtekind == (b)->rtekind && \ + (a)->rtekind == RTE_RELATION && \ + (a)->relid == (b)->relid \ +) + + +/* + * Any self join or join with aggregation over the same table + */ + +static CorrelationKind +get_correlation_kind(PlannerInfo *root, int varRelid, OpExpr* expr) +{ + Node *left_arg, *right_arg; + Relids left_varnos, right_varnos; + int left_varno, right_varno; + RangeTblEntry *left_rte, *right_rte; + + if (varRelid != 0) + /* We consider only case of joins, not restriction mode */ + return CKIndepend; + + /* Check if it is equality comparison */ + if (get_oprrest(expr->opno) != F_EQSEL) + return CKIndepend; + + left_arg = linitial(expr->args); + right_arg = lsecond(expr->args); + + /* + * Check if it is join of two different relations + */ + left_varnos = pull_varnos(root, left_arg); + right_varnos = pull_varnos(root, right_arg); + if (!bms_get_singleton_member(left_varnos, &left_varno) || + !bms_get_singleton_member(right_varnos, &right_varno) || + left_varno == right_varno) + return CKIndepend; + + left_rte = planner_rt_fetch(left_varno, root); + right_rte = planner_rt_fetch(right_varno, root); + + if (IsSameRelationRTE(left_rte, right_rte)) + { + Var *lvar = get_var(left_arg), + *rvar = get_var(right_arg); + + /* self join detected, check if it simple a=b clause */ + if (lvar == NULL || rvar == NULL) + return CKLikelySelf; + return (lvar->varattno == rvar->varattno) ? + CKSelf : CKLikelySelf; + } + + if ((left_rte = find_single_rte(left_rte)) == NULL) + return CKIndepend; + if ((right_rte = find_single_rte(right_rte)) == NULL) + return CKIndepend; + + if (IsSameRelationRTE(left_rte, right_rte)) + { + /* self join detected, but over some transformation which cannot be + * flatten */ + return CKLikelySelf; + } + + return CKIndepend; +} /* * clause_selectivity - diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c index 0ba26b207b0..22cd37cf3e2 100644 --- a/src/backend/optimizer/path/costsize.c +++ b/src/backend/optimizer/path/costsize.c @@ -157,6 +157,7 @@ typedef struct { PlannerInfo *root; QualCost total; + bool calccoalesce; } cost_qual_eval_context; static List *extract_nonindex_conditions(List *qual_clauses, List *indexclauses); @@ -765,7 +766,7 @@ cost_index(IndexPath *path, PlannerInfo *root, double loop_count, cost_qual_eval(&qpqual_cost, qpquals, root); startup_cost += qpqual_cost.startup; - cpu_per_tuple = cpu_tuple_cost + qpqual_cost.per_tuple; + cpu_per_tuple = cpu_tuple_cost + 2.0*qpqual_cost.per_tuple; cpu_run_cost += cpu_per_tuple * tuples_fetched; @@ -1051,7 +1052,7 @@ cost_bitmap_heap_scan(Path *path, PlannerInfo *root, RelOptInfo *baserel, get_restriction_qual_cost(root, baserel, param_info, &qpqual_cost); startup_cost += qpqual_cost.startup; - cpu_per_tuple = cpu_tuple_cost + qpqual_cost.per_tuple; + cpu_per_tuple = cpu_tuple_cost + 2.0*qpqual_cost.per_tuple; cpu_run_cost = cpu_per_tuple * tuples_fetched; /* Adjust costing for parallelism, if used. */ @@ -1796,6 +1797,327 @@ cost_recursive_union(Path *runion, Path *nrterm, Path *rterm) rterm->pathtarget->width); } +/* + * is_fake_var + * Workaround for generate_append_tlist() which generates fake Vars with + * varno == 0, that will cause a fail of estimate_num_group() call + * + * XXX Ummm, why would estimate_num_group fail with this? + */ +static bool +is_fake_var(Expr *expr) +{ + if (IsA(expr, RelabelType)) + expr = (Expr *) ((RelabelType *) expr)->arg; + + return (IsA(expr, Var) && ((Var *) expr)->varno == 0); +} + +/* + * get_width_cost_multiplier + * Returns relative complexity of comparing two values based on its width. + * The idea behind is that the comparison becomes more expensive the longer the + * value is. Return value is in cpu_operator_cost units. + */ +static double +get_width_cost_multiplier(PlannerInfo *root, Expr *expr) +{ + double width = -1.0; /* fake value */ + + if (IsA(expr, RelabelType)) + expr = (Expr *) ((RelabelType *) expr)->arg; + + /* Try to find actual stat in corresponding relation */ + if (IsA(expr, Var)) + { + Var *var = (Var *) expr; + + if (var->varno > 0 && var->varno < root->simple_rel_array_size) + { + RelOptInfo *rel = root->simple_rel_array[var->varno]; + + if (rel != NULL && + var->varattno >= rel->min_attr && + var->varattno <= rel->max_attr) + { + int ndx = var->varattno - rel->min_attr; + + if (rel->attr_widths[ndx] > 0) + width = rel->attr_widths[ndx]; + } + } + } + + /* Didn't find any actual stats, try using type width instead. */ + if (width < 0.0) + { + Node *node = (Node *) expr; + + width = get_typavgwidth(exprType(node), exprTypmod(node)); + } + + /* + * Values are passed as Datum type, so comparisons can't be cheaper than + * comparing a Datum value. + * + * FIXME I find this reasoning questionable. We may pass int2, and + * comparing it is probably a bit cheaper than comparing a bigint. + */ + if (width <= sizeof(Datum)) + return 1.0; + + /* + * We consider the cost of a comparison not to be directly proportional to + * width of the argument, because widths of the arguments could be + * slightly different (we only know the average width for the whole + * column). So we use log16(width) as an estimate. + */ + return 1.0 + 0.125 * LOG2(width / sizeof(Datum)); +} + +/* + * compute_cpu_sort_cost + * compute CPU cost of sort (i.e. in-memory) + * + * The main thing we need to calculate to estimate sort CPU costs is the number + * of calls to the comparator functions. The difficulty is that for multi-column + * sorts there may be different data types involved (for some of which the calls + * may be much more expensive). Furthermore, columns may have a very different + * number of distinct values - the higher the number, the fewer comparisons will + * be needed for the following columns. + * + * The algorithm is incremental - we add pathkeys one by one, and at each step we + * estimate the number of necessary comparisons (based on the number of distinct + * groups in the current pathkey prefix and the new pathkey), and the comparison + * costs (which is data type specific). + * + * Estimation of the number of comparisons is based on ideas from: + * + * "Quicksort Is Optimal", Robert Sedgewick, Jon Bentley, 2002 + * [https://www.cs.princeton.edu/~rs/talks/QuicksortIsOptimal.pdf] + * + * In term of that paper, let N - number of tuples, Xi - number of identical + * tuples with value Ki, then the estimate of number of comparisons is: + * + * log(N! / (X1! * X2! * ..)) ~ sum(Xi * log(N/Xi)) + * + * We assume all Xi the same because now we don't have any estimation of + * group sizes, we have only know the estimate of number of groups (distinct + * values). In that case, formula becomes: + * + * N * log(NumberOfGroups) + * + * For multi-column sorts we need to estimate the number of comparisons for + * each individual column - for example with columns (c1, c2, ..., ck) we + * can estimate that number of comparisons on ck is roughly + * + * ncomparisons(c1, c2, ..., ck) / ncomparisons(c1, c2, ..., c(k-1)) + * + * Let k be a column number, Gk - number of groups defined by k columns, and Fk + * the cost of the comparison is + * + * N * sum( Fk * log(Gk) ) + * + * Note: We also consider column width, not just the comparator cost. + * + * NOTE: some callers currently pass NIL for pathkeys because they + * can't conveniently supply the sort keys. In this case, it will fallback to + * simple comparison cost estimate. + */ +static Cost +compute_cpu_sort_cost(PlannerInfo *root, List *pathkeys, int nPresortedKeys, + Cost comparison_cost, double tuples, double output_tuples, + bool heapSort) +{ + Cost per_tuple_cost = 0.0; + ListCell *lc; + List *pathkeyExprs = NIL; + double tuplesPerPrevGroup = tuples; + double totalFuncCost = 1.0; + bool has_fake_var = false; + int i = 0; + Oid prev_datatype = InvalidOid; + List *cache_varinfos = NIL; + + /* fallback if pathkeys is unknown */ + if (list_length(pathkeys) == 0) + { + /* + * If we'll use a bounded heap-sort keeping just K tuples in memory, + * for a total number of tuple comparisons of N log2 K; but the + * constant factor is a bit higher than for quicksort. Tweak it so + * that the cost curve is continuous at the crossover point. + */ + output_tuples = (heapSort) ? 2.0 * output_tuples : tuples; + per_tuple_cost += 2.0 * cpu_operator_cost * LOG2(output_tuples); + + /* add cost provided by caller */ + per_tuple_cost += comparison_cost; + + return per_tuple_cost * tuples; + } + + /* + * Computing total cost of sorting takes into account the per-column + * comparison function cost. We try to compute the needed number of + * comparisons per column. + */ + foreach(lc, pathkeys) + { + PathKey *pathkey = (PathKey *) lfirst(lc); + EquivalenceMember *em; + double nGroups, + correctedNGroups; + Cost funcCost = 1.0; + + /* + * We believe that equivalence members aren't very different, so, to + * estimate cost we consider just the first member. + */ + em = (EquivalenceMember *) linitial(pathkey->pk_eclass->ec_members); + + if (em->em_datatype != InvalidOid) + { + /* do not lookup funcCost if the data type is the same */ + if (prev_datatype != em->em_datatype) + { + Oid sortop; + QualCost cost; + + sortop = get_opfamily_member(pathkey->pk_opfamily, + em->em_datatype, em->em_datatype, + pathkey->pk_strategy); + + cost.startup = 0; + cost.per_tuple = 0; + add_function_cost(root, get_opcode(sortop), NULL, &cost); + + /* + * add_function_cost returns the product of cpu_operator_cost + * and procost, but we need just procost, co undo that. + */ + funcCost = cost.per_tuple / cpu_operator_cost; + + prev_datatype = em->em_datatype; + } + } + + /* factor in the width of the values in this column */ + funcCost *= get_width_cost_multiplier(root, em->em_expr); + + /* now we have per-key cost, so add to the running total */ + totalFuncCost += funcCost; + + /* remember if we have found a fake Var in pathkeys */ + has_fake_var |= is_fake_var(em->em_expr); + pathkeyExprs = lappend(pathkeyExprs, em->em_expr); + + /* + * We need to calculate the number of comparisons for this column, + * which requires knowing the group size. So we estimate the number of + * groups by calling estimate_num_groups_incremental(), which + * estimates the group size for "new" pathkeys. + * + * Note: estimate_num_groups_incremental does not handle fake Vars, so + * use a default estimate otherwise. + */ + if (!has_fake_var) + nGroups = estimate_num_groups_incremental(root, pathkeyExprs, + tuplesPerPrevGroup, NULL, NULL, + &cache_varinfos, + list_length(pathkeyExprs) - 1); + else if (tuples > 4.0) + + /* + * Use geometric mean as estimation if there are no stats. + * + * We don't use DEFAULT_NUM_DISTINCT here, because that's used for + * a single column, but here we're dealing with multiple columns. + */ + nGroups = ceil(2.0 + sqrt(tuples) * (i + 1) / list_length(pathkeys)); + else + nGroups = tuples; + + /* + * Presorted keys are not considered in the cost above, but we still + * do have to compare them in the qsort comparator. So make sure to + * factor in the cost in that case. + */ + if (i >= nPresortedKeys) + { + if (heapSort) + { + /* + * have to keep at least one group, and a multiple of group + * size + */ + correctedNGroups = ceil(output_tuples / tuplesPerPrevGroup); + } + else + /* all groups in the input */ + correctedNGroups = nGroups; + + correctedNGroups = Max(1.0, ceil(correctedNGroups)); + + per_tuple_cost += totalFuncCost * LOG2(correctedNGroups); + } + + i++; + + /* + * Uniform distributions with all groups being of the same size are + * the best case, with nice smooth behavior. Real-world distributions + * tend not to be uniform, though, and we don't have any reliable + * easy-to-use information. As a basic defense against skewed + * distributions, we use a 1.5 factor to make the expected group a bit + * larger, but we need to be careful not to make the group larger than + * in the preceding step. + */ + tuplesPerPrevGroup = Min(tuplesPerPrevGroup, + ceil(1.5 * tuplesPerPrevGroup / nGroups)); + + /* + * Once we get single-row group, it means tuples in the group are + * unique and we can skip all remaining columns. + */ + if (tuplesPerPrevGroup <= 1.0) + break; + } + + list_free(pathkeyExprs); + + /* per_tuple_cost is in cpu_operator_cost units */ + per_tuple_cost *= cpu_operator_cost; + + /* + * Accordingly to "Introduction to algorithms", Thomas H. Cormen, Charles + * E. Leiserson, Ronald L. Rivest, ISBN 0-07-013143-0, quicksort + * estimation formula has additional term proportional to number of tuples + * (see Chapter 8.2 and Theorem 4.1). That affects cases with a low number + * of tuples, approximately less than 1e4. We could implement it as an + * additional multiplier under the logarithm, but we use a bit more + * complex formula which takes into account the number of unique tuples + * and it's not clear how to combine the multiplier with the number of + * groups. Estimate it as 10 cpu_operator_cost units. + */ + per_tuple_cost += 10 * cpu_operator_cost; + + per_tuple_cost += comparison_cost; + + return tuples * per_tuple_cost; +} + +/* + * simple wrapper just to estimate best sort path + */ +Cost +cost_sort_estimate(PlannerInfo *root, List *pathkeys, int nPresortedKeys, + double tuples) +{ + return compute_cpu_sort_cost(root, pathkeys, nPresortedKeys, + 0, tuples, tuples, false); +} + /* * cost_tuplesort * Determines and returns the cost of sorting a relation using tuplesort, @@ -1812,7 +2134,7 @@ cost_recursive_union(Path *runion, Path *nrterm, Path *rterm) * number of initial runs formed and M is the merge order used by tuplesort.c. * Since the average initial run should be about sort_mem, we have * disk traffic = 2 * relsize * ceil(logM(p / sort_mem)) - * cpu = comparison_cost * t * log2(t) + * and cpu cost (computed by compute_cpu_sort_cost()). * * If the sort is bounded (i.e., only the first k result tuples are needed) * and k tuples can fit into sort_mem, we use a heap method that keeps only @@ -1831,9 +2153,11 @@ cost_recursive_union(Path *runion, Path *nrterm, Path *rterm) * 'comparison_cost' is the extra cost per comparison, if any * 'sort_mem' is the number of kilobytes of work memory allowed for the sort * 'limit_tuples' is the bound on the number of output tuples; -1 if no bound + * 'startup_cost' is expected to be 0 at input. If there is "input cost" it should + * be added by caller later */ static void -cost_tuplesort(Cost *startup_cost, Cost *run_cost, +cost_tuplesort(PlannerInfo *root, List *pathkeys, Cost *startup_cost, Cost *run_cost, double tuples, int width, Cost comparison_cost, int sort_mem, double limit_tuples) @@ -1850,9 +2174,6 @@ cost_tuplesort(Cost *startup_cost, Cost *run_cost, if (tuples < 2.0) tuples = 2.0; - /* Include the default cost-per-comparison */ - comparison_cost += 2.0 * cpu_operator_cost; - /* Do we have a useful LIMIT? */ if (limit_tuples > 0 && limit_tuples < tuples) { @@ -1876,12 +2197,10 @@ cost_tuplesort(Cost *startup_cost, Cost *run_cost, double log_runs; double npageaccesses; - /* - * CPU costs - * - * Assume about N log2 N comparisons - */ - *startup_cost = comparison_cost * tuples * LOG2(tuples); + /* CPU costs */ + *startup_cost = compute_cpu_sort_cost(root, pathkeys, 0, + comparison_cost, tuples, + tuples, false); /* Disk costs */ @@ -1897,18 +2216,17 @@ cost_tuplesort(Cost *startup_cost, Cost *run_cost, } else if (tuples > 2 * output_tuples || input_bytes > sort_mem_bytes) { - /* - * We'll use a bounded heap-sort keeping just K tuples in memory, for - * a total number of tuple comparisons of N log2 K; but the constant - * factor is a bit higher than for quicksort. Tweak it so that the - * cost curve is continuous at the crossover point. - */ - *startup_cost = comparison_cost * tuples * LOG2(2.0 * output_tuples); + /* We'll use a bounded heap-sort keeping just K tuples in memory. */ + *startup_cost = compute_cpu_sort_cost(root, pathkeys, 0, + comparison_cost, tuples, + output_tuples, true); } else { /* We'll use plain quicksort on all the input tuples */ - *startup_cost = comparison_cost * tuples * LOG2(tuples); + *startup_cost = compute_cpu_sort_cost(root, pathkeys, 0, + comparison_cost, tuples, + tuples, false); } /* @@ -1941,8 +2259,8 @@ cost_incremental_sort(Path *path, double input_tuples, int width, Cost comparison_cost, int sort_mem, double limit_tuples) { - Cost startup_cost = 0, - run_cost = 0, + Cost startup_cost, + run_cost, input_run_cost = input_total_cost - input_startup_cost; double group_tuples, input_groups; @@ -2027,7 +2345,7 @@ cost_incremental_sort(Path *path, * pessimistic about incremental sort performance and increase its average * group size by half. */ - cost_tuplesort(&group_startup_cost, &group_run_cost, + cost_tuplesort(root, pathkeys, &group_startup_cost, &group_run_cost, 1.5 * group_tuples, width, comparison_cost, sort_mem, limit_tuples); @@ -2035,7 +2353,7 @@ cost_incremental_sort(Path *path, * Startup cost of incremental sort is the startup cost of its first group * plus the cost of its input. */ - startup_cost += group_startup_cost + startup_cost = group_startup_cost + input_startup_cost + group_input_run_cost; /* @@ -2044,7 +2362,7 @@ cost_incremental_sort(Path *path, * group, plus the total cost to process the remaining groups, plus the * remaining cost of input. */ - run_cost += group_run_cost + run_cost = group_run_cost + (group_run_cost + group_startup_cost) * (input_groups - 1) + group_input_run_cost * (input_groups - 1); @@ -2084,7 +2402,7 @@ cost_sort(Path *path, PlannerInfo *root, Cost startup_cost; Cost run_cost; - cost_tuplesort(&startup_cost, &run_cost, + cost_tuplesort(root, pathkeys, &startup_cost, &run_cost, tuples, width, comparison_cost, sort_mem, limit_tuples); @@ -2182,7 +2500,7 @@ append_nonpartial_cost(List *subpaths, int numpaths, int parallel_workers) * Determines and returns the cost of an Append node. */ void -cost_append(AppendPath *apath) +cost_append(AppendPath *apath, PlannerInfo *root) { ListCell *l; @@ -2250,7 +2568,7 @@ cost_append(AppendPath *apath) * any child. */ cost_sort(&sort_path, - NULL, /* doesn't currently need root */ + root, pathkeys, subpath->total_cost, subpath->rows, @@ -3413,8 +3731,9 @@ initial_cost_mergejoin(PlannerInfo *root, JoinCostWorkspace *workspace, /* CPU costs left for later */ /* Public result fields */ - workspace->startup_cost = startup_cost; - workspace->total_cost = startup_cost + run_cost + inner_run_cost; + workspace->startup_cost = startup_cost + outer_path->total_cost/outer_rows + + inner_path->total_cost/inner_rows; + workspace->total_cost = workspace->startup_cost + run_cost + inner_run_cost; /* Save private data for final_cost_mergejoin */ workspace->run_cost = run_cost; workspace->inner_run_cost = inner_run_cost; @@ -4355,6 +4674,7 @@ cost_qual_eval(QualCost *cost, List *quals, PlannerInfo *root) context.root = root; context.total.startup = 0; context.total.per_tuple = 0; + context.calccoalesce = true; /* We don't charge any cost for the implicit ANDing at top level ... */ @@ -4380,6 +4700,22 @@ cost_qual_eval_node(QualCost *cost, Node *qual, PlannerInfo *root) context.root = root; context.total.startup = 0; context.total.per_tuple = 0; + context.calccoalesce = true; + + cost_qual_eval_walker(qual, &context); + + *cost = context.total; +} + +void +cost_qual_eval_node_index(QualCost *cost, Node *qual, PlannerInfo *root) +{ + cost_qual_eval_context context; + + context.root = root; + context.total.startup = 0; + context.total.per_tuple = 0; + context.calccoalesce = false; cost_qual_eval_walker(qual, &context); @@ -4409,6 +4745,7 @@ cost_qual_eval_walker(Node *node, cost_qual_eval_context *context) locContext.root = context->root; locContext.total.startup = 0; locContext.total.per_tuple = 0; + locContext.calccoalesce = context->calccoalesce; /* * For an OR clause, recurse into the marked-up tree so that we @@ -4649,6 +4986,11 @@ cost_qual_eval_walker(Node *node, cost_qual_eval_context *context) */ return false; } + else if (IsA(node, CoalesceExpr) && context->calccoalesce) + { + context->total.per_tuple += cpu_operator_cost * + list_length(((CoalesceExpr *) node)->args); + } /* recurse into children */ return expression_tree_walker(node, cost_qual_eval_walker, @@ -5136,6 +5478,7 @@ calc_joinrel_size_estimate(PlannerInfo *root, Selectivity jselec; Selectivity pselec; double nrows; + bool apply_righthand = false; /* * Compute joinclause selectivity. Note that we are only considering @@ -5174,9 +5517,11 @@ calc_joinrel_size_estimate(PlannerInfo *root, { RestrictInfo *rinfo = lfirst_node(RestrictInfo, l); - if (RINFO_IS_PUSHED_DOWN(rinfo, joinrel->relids)) + if (RINFO_IS_PUSHED_DOWN(rinfo, joinrel->relids)) { pushedquals = lappend(pushedquals, rinfo); - else + apply_righthand |= bms_overlap(rinfo->clause_relids, + sjinfo->min_righthand); + } else joinquals = lappend(joinquals, rinfo); } @@ -5228,6 +5573,8 @@ calc_joinrel_size_estimate(PlannerInfo *root, nrows = outer_rows * inner_rows * fkselec * jselec; if (nrows < outer_rows) nrows = outer_rows; + if (apply_righthand && inner_rows < outer_rows) + pselec *= inner_rows / outer_rows; nrows *= pselec; break; case JOIN_FULL: diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c index 9f39f4661a4..79912955485 100644 --- a/src/backend/optimizer/path/equivclass.c +++ b/src/backend/optimizer/path/equivclass.c @@ -681,7 +681,18 @@ get_eclass_for_sort_expr(PlannerInfo *root, if (opcintype == cur_em->em_datatype && equal(expr, cur_em->em_expr)) - return cur_ec; /* Match! */ + { + /* + * Match! + * + * Copy the sortref if it wasn't set yet. That may happen if + * the ec was constructed from WHERE clause, i.e. it doesn't + * have a target reference at all. + */ + if (cur_ec->ec_sortref == 0 && sortref > 0) + cur_ec->ec_sortref = sortref; + return cur_ec; + } } } diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c index 3800f0cdfb7..48617bf15c2 100644 --- a/src/backend/optimizer/path/indxpath.c +++ b/src/backend/optimizer/path/indxpath.c @@ -111,8 +111,6 @@ static List *build_index_paths(PlannerInfo *root, RelOptInfo *rel, bool *skip_lower_saop); static List *build_paths_for_OR(PlannerInfo *root, RelOptInfo *rel, List *clauses, List *other_clauses); -static List *generate_bitmap_or_paths(PlannerInfo *root, RelOptInfo *rel, - List *clauses, List *other_clauses); static Path *choose_bitmap_and(PlannerInfo *root, RelOptInfo *rel, List *paths); static int path_usage_comparator(const void *a, const void *b); @@ -1251,7 +1249,7 @@ build_paths_for_OR(PlannerInfo *root, RelOptInfo *rel, * for the purpose of generating indexquals, but are not to be searched for * ORs. (See build_paths_for_OR() for motivation.) */ -static List * +List * generate_bitmap_or_paths(PlannerInfo *root, RelOptInfo *rel, List *clauses, List *other_clauses) { @@ -3277,7 +3275,6 @@ match_clause_to_ordering_op(IndexOptInfo *index, return clause; } - /**************************************************************************** * ---- ROUTINES TO DO PARTIAL INDEX PREDICATE TESTS ---- ****************************************************************************/ @@ -3477,7 +3474,8 @@ ec_member_matches_indexcol(PlannerInfo *root, RelOptInfo *rel, * relation_has_unique_index_for * Determine whether the relation provably has at most one row satisfying * a set of equality conditions, because the conditions constrain all - * columns of some unique index. + * columns of some unique index. If index_info is not null, it is set to + * point to a new UniqueIndexInfo containing the index and conditions. * * The conditions can be represented in either or both of two ways: * 1. A list of RestrictInfo nodes, where the caller has already determined @@ -3498,7 +3496,8 @@ ec_member_matches_indexcol(PlannerInfo *root, RelOptInfo *rel, bool relation_has_unique_index_for(PlannerInfo *root, RelOptInfo *rel, List *restrictlist, - List *exprlist, List *oprlist) + List *exprlist, List *oprlist, + UniqueIndexInfo **index_info) { ListCell *ic; @@ -3554,6 +3553,7 @@ relation_has_unique_index_for(PlannerInfo *root, RelOptInfo *rel, { IndexOptInfo *ind = (IndexOptInfo *) lfirst(ic); int c; + List *matched_restrictlist = NIL; /* * If the index is not unique, or not immediately enforced, or if it's @@ -3605,6 +3605,7 @@ relation_has_unique_index_for(PlannerInfo *root, RelOptInfo *rel, if (match_index_to_operand(rexpr, c, ind)) { matched = true; /* column is unique */ + matched_restrictlist = lappend(matched_restrictlist, rinfo); break; } } @@ -3647,7 +3648,25 @@ relation_has_unique_index_for(PlannerInfo *root, RelOptInfo *rel, /* Matched all key columns of this index? */ if (c == ind->nkeycolumns) + { + if (index_info != NULL) + { + /* This may be called in GEQO memory context. */ + MemoryContext oldContext = MemoryContextSwitchTo(root->planner_cxt); + *index_info = palloc(sizeof(UniqueIndexInfo)); + (*index_info)->index = ind; + (*index_info)->clauses = list_copy(matched_restrictlist); + MemoryContextSwitchTo(oldContext); + } + + if (matched_restrictlist) + list_free(matched_restrictlist); + return true; + } + + if (matched_restrictlist) + list_free(matched_restrictlist); } return false; diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c index bad3dd1a736..e8bf85dcafb 100644 --- a/src/backend/optimizer/path/joinpath.c +++ b/src/backend/optimizer/path/joinpath.c @@ -189,7 +189,8 @@ add_paths_to_joinrel(PlannerInfo *root, innerrel, JOIN_INNER, restrictlist, - false); + false, + NULL /*index_info*/); break; default: extra.inner_unique = innerrel_is_unique(root, @@ -198,7 +199,8 @@ add_paths_to_joinrel(PlannerInfo *root, innerrel, jointype, restrictlist, - false); + false, + NULL /*index_info*/); break; } diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index 9da3ff2f9ab..b83520f509a 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -1279,7 +1279,7 @@ mark_dummy_rel(RelOptInfo *rel) /* Set up the dummy path */ add_path(rel, (Path *) create_append_path(NULL, rel, NIL, NIL, NIL, rel->lateral_relids, - 0, false, -1)); + 0, false, -1, false)); /* Set or update cheapest_total_path and related fields */ set_cheapest(rel); diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c index 86a35cdef17..43bbe73eb3a 100644 --- a/src/backend/optimizer/path/pathkeys.c +++ b/src/backend/optimizer/path/pathkeys.c @@ -17,17 +17,24 @@ */ #include "postgres.h" +#include <float.h> + +#include "miscadmin.h" #include "access/stratnum.h" #include "catalog/pg_opfamily.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" #include "nodes/plannodes.h" +#include "optimizer/cost.h" #include "optimizer/optimizer.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "partitioning/partbounds.h" #include "utils/lsyscache.h" +#include "utils/selfuncs.h" +/* Consider reordering of GROUP BY keys? */ +bool enable_group_by_reordering = true; static bool pathkey_is_redundant(PathKey *new_pathkey, List *pathkeys); static bool matches_boolean_partition_clause(RestrictInfo *rinfo, @@ -334,6 +341,527 @@ pathkeys_contained_in(List *keys1, List *keys2) return false; } +/* + * group_keys_reorder_by_pathkeys + * Reorder GROUP BY keys to match pathkeys of input path. + * + * Function returns new lists (pathkeys and clauses), original GROUP BY lists + * stay untouched. + * + * Returns the number of GROUP BY keys with a matching pathkey. + */ +int +group_keys_reorder_by_pathkeys(List *pathkeys, List **group_pathkeys, + List **group_clauses) +{ + List *new_group_pathkeys = NIL, + *new_group_clauses = NIL; + ListCell *lc; + int n; + + if (pathkeys == NIL || *group_pathkeys == NIL) + return 0; + + /* + * Walk the pathkeys (determining ordering of the input path) and see if + * there's a matching GROUP BY key. If we find one, we append it to the + * list, and do the same for the clauses. + * + * Once we find the first pathkey without a matching GROUP BY key, the + * rest of the pathkeys are useless and can't be used to evaluate the + * grouping, so we abort the loop and ignore the remaining pathkeys. + * + * XXX Pathkeys are built in a way to allow simply comparing pointers. + */ + foreach(lc, pathkeys) + { + PathKey *pathkey = (PathKey *) lfirst(lc); + SortGroupClause *sgc; + + /* abort on first mismatch */ + if (!list_member_ptr(*group_pathkeys, pathkey)) + break; + + new_group_pathkeys = lappend(new_group_pathkeys, pathkey); + + sgc = get_sortgroupref_clause(pathkey->pk_eclass->ec_sortref, + *group_clauses); + + new_group_clauses = lappend(new_group_clauses, sgc); + } + + /* remember the number of pathkeys with a matching GROUP BY key */ + n = list_length(new_group_pathkeys); + + /* append the remaining group pathkeys (will be treated as not sorted) */ + *group_pathkeys = list_concat_unique_ptr(new_group_pathkeys, + *group_pathkeys); + *group_clauses = list_concat_unique_ptr(new_group_clauses, + *group_clauses); + + return n; +} + +/* + * Used to generate all permutations of a pathkey list. + */ +typedef struct PathkeyMutatorState +{ + List *elemsList; + ListCell **elemCells; + void **elems; + int *positions; + int mutatorNColumns; + int count; +} PathkeyMutatorState; + + +/* + * PathkeyMutatorInit + * Initialize state of the permutation generator. + * + * We want to generate permutations of elements in the "elems" list. We may want + * to skip some number of elements at the beginning (when treating as presorted) + * or at the end (we only permute a limited number of group keys). + * + * The list is decomposed into elements, and we also keep pointers to individual + * cells. This allows us to build the permuted list quickly and cheaply, without + * creating any copies. + */ +static void +PathkeyMutatorInit(PathkeyMutatorState *state, List *elems, int start, int end) +{ + int i; + int n = end - start; + ListCell *lc; + + memset(state, 0, sizeof(*state)); + + state->mutatorNColumns = n; + + state->elemsList = list_copy(elems); + + state->elems = palloc(sizeof(void *) * n); + state->elemCells = palloc(sizeof(ListCell *) * n); + state->positions = palloc(sizeof(int) * n); + + i = 0; + for_each_cell(lc, state->elemsList, list_nth_cell(state->elemsList, start)) + { + state->elemCells[i] = lc; + state->elems[i] = lfirst(lc); + state->positions[i] = i + 1; + i++; + + if (i >= n) + break; + } +} + +/* Swap two elements of an array. */ +static void +PathkeyMutatorSwap(int *a, int i, int j) +{ + int s = a[i]; + + a[i] = a[j]; + a[j] = s; +} + +/* + * Generate the next permutation of elements. + */ +static bool +PathkeyMutatorNextSet(int *a, int n) +{ + int j, + k, + l, + r; + + j = n - 2; + + while (j >= 0 && a[j] >= a[j + 1]) + j--; + + if (j < 0) + return false; + + k = n - 1; + + while (k >= 0 && a[j] >= a[k]) + k--; + + PathkeyMutatorSwap(a, j, k); + + l = j + 1; + r = n - 1; + + while (l < r) + PathkeyMutatorSwap(a, l++, r--); + + return true; +} + +/* + * PathkeyMutatorNext + * Generate the next permutation of list of elements. + * + * Returns the next permutation (as a list of elements) or NIL if there are no + * more permutations. + */ +static List * +PathkeyMutatorNext(PathkeyMutatorState *state) +{ + int i; + + state->count++; + + /* first permutation is original list */ + if (state->count == 1) + return state->elemsList; + + /* when there are no more permutations, return NIL */ + if (!PathkeyMutatorNextSet(state->positions, state->mutatorNColumns)) + { + pfree(state->elems); + pfree(state->elemCells); + pfree(state->positions); + + list_free(state->elemsList); + + return NIL; + } + + /* update the list cells to point to the right elements */ + for (i = 0; i < state->mutatorNColumns; i++) + lfirst(state->elemCells[i]) = + (void *) state->elems[state->positions[i] - 1]; + + return state->elemsList; +} + +/* + * Cost of comparing pathkeys. + */ +typedef struct PathkeySortCost +{ + Cost cost; + PathKey *pathkey; +} PathkeySortCost; + +static int +pathkey_sort_cost_comparator(const void *_a, const void *_b) +{ + const PathkeySortCost *a = (PathkeySortCost *) _a; + const PathkeySortCost *b = (PathkeySortCost *) _b; + + if (a->cost < b->cost) + return -1; + else if (a->cost == b->cost) + return 0; + return 1; +} + +/* + * get_cheapest_group_keys_order + * Reorders the group pathkeys / clauses to minimize the comparison cost. + * + * Given the list of pathkeys in '*group_pathkeys', we try to arrange these + * in an order that minimizes the sort costs that will be incurred by the + * GROUP BY. The costs mainly depend on the cost of the sort comparator + * function(s) and the number of distinct values in each column of the GROUP + * BY clause (*group_clauses). Sorting on subsequent columns is only required + * for tiebreak situations where two values sort equally. + * + * In case the input is partially sorted, only the remaining pathkeys are + * considered. 'n_preordered' denotes how many of the leading *group_pathkeys + * the input is presorted by. + * + * Returns true and sets *group_pathkeys and *group_clauses to the newly + * ordered versions of the lists that were passed in via these parameters. + * If no reordering was deemed necessary then we return false, in which case + * the *group_pathkeys and *group_clauses lists are left untouched. The + * original *group_pathkeys and *group_clauses parameter values are never + * destructively modified in place. + */ +static bool +get_cheapest_group_keys_order(PlannerInfo *root, double nrows, + List **group_pathkeys, List **group_clauses, + int n_preordered) +{ + List *new_group_pathkeys = NIL, + *new_group_clauses = NIL, + *var_group_pathkeys; + + ListCell *cell; + PathkeyMutatorState mstate; + double cheapest_sort_cost = DBL_MAX; + + int nFreeKeys; + int nToPermute; + + /* If there are less than 2 unsorted pathkeys, we're done. */ + if (list_length(*group_pathkeys) - n_preordered < 2) + return false; + + /* + * We could exhaustively cost all possible orderings of the pathkeys, but + * for a large number of pathkeys it might be prohibitively expensive. So + * we try to apply simple cheap heuristics first - we sort the pathkeys by + * sort cost (as if the pathkey was sorted independently) and then check + * only the four cheapest pathkeys. The remaining pathkeys are kept + * ordered by cost. + * + * XXX This is a very simple heuristics, but likely to work fine for most + * cases (because the number of GROUP BY clauses tends to be lower than + * 4). But it ignores how the number of distinct values in each pathkey + * affects the following steps. It might be better to use "more expensive" + * pathkey first if it has many distinct values, because it then limits + * the number of comparisons for the remaining pathkeys. But evaluating + * that is likely quite the expensive. + */ + nFreeKeys = list_length(*group_pathkeys) - n_preordered; + nToPermute = 4; + if (nFreeKeys > nToPermute) + { + PathkeySortCost *costs = palloc(sizeof(PathkeySortCost) * nFreeKeys); + PathkeySortCost *cost = costs; + + /* + * Estimate cost for sorting individual pathkeys skipping the + * pre-ordered pathkeys. + */ + for_each_from(cell, *group_pathkeys, n_preordered) + { + PathKey *pathkey = (PathKey *) lfirst(cell); + List *to_cost = list_make1(pathkey); + + cost->pathkey = pathkey; + cost->cost = cost_sort_estimate(root, to_cost, 0, nrows); + cost++; + + list_free(to_cost); + } + + /* sort the pathkeys by sort cost in ascending order */ + qsort(costs, nFreeKeys, sizeof(*costs), pathkey_sort_cost_comparator); + + /* + * Rebuild the list of pathkeys - first the preordered ones, then the + * rest ordered by cost. + */ + new_group_pathkeys = list_copy_head(*group_pathkeys, n_preordered); + + for (int i = 0; i < nFreeKeys; i++) + new_group_pathkeys = lappend(new_group_pathkeys, costs[i].pathkey); + + pfree(costs); + } + else + { + /* Copy the list, so that we can free the new list by list_free. */ + new_group_pathkeys = list_copy(*group_pathkeys); + nToPermute = nFreeKeys; + } + + Assert(list_length(new_group_pathkeys) == list_length(*group_pathkeys)); + + /* + * Generate pathkey lists with permutations of the first nToPermute + * pathkeys. + * + * XXX We simply calculate sort cost for each individual pathkey list, but + * there's room for two dynamic programming optimizations here. Firstly, + * we may pass the current "best" cost to cost_sort_estimate so that it + * can "abort" if the estimated pathkeys list exceeds it. Secondly, it + * could pass the return information about the position when it exceeded + * the cost, and we could skip all permutations with the same prefix. + * + * Imagine we've already found ordering with cost C1, and we're evaluating + * another ordering - cost_sort_estimate() calculates cost by adding the + * pathkeys one by one (more or less), and the cost only grows. If at any + * point it exceeds C1, it can't possibly be "better" so we can discard + * it. But we also know that we can discard all ordering with the same + * prefix, because if we're estimating (a,b,c,d) and we exceed C1 at (a,b) + * then the same thing will happen for any ordering with this prefix. + */ + PathkeyMutatorInit(&mstate, new_group_pathkeys, n_preordered, n_preordered + nToPermute); + + while ((var_group_pathkeys = PathkeyMutatorNext(&mstate)) != NIL) + { + Cost cost; + + cost = cost_sort_estimate(root, var_group_pathkeys, n_preordered, nrows); + + if (cost < cheapest_sort_cost) + { + list_free(new_group_pathkeys); + new_group_pathkeys = list_copy(var_group_pathkeys); + cheapest_sort_cost = cost; + } + } + + /* Reorder the group clauses according to the reordered pathkeys. */ + foreach(cell, new_group_pathkeys) + { + PathKey *pathkey = (PathKey *) lfirst(cell); + + new_group_clauses = lappend(new_group_clauses, + get_sortgroupref_clause(pathkey->pk_eclass->ec_sortref, + *group_clauses)); + } + + /* Just append the rest GROUP BY clauses */ + new_group_clauses = list_concat_unique_ptr(new_group_clauses, + *group_clauses); + + *group_pathkeys = new_group_pathkeys; + *group_clauses = new_group_clauses; + + return true; +} + +/* + * get_useful_group_keys_orderings + * Determine which orderings of GROUP BY keys are potentially interesting. + * + * Returns list of PathKeyInfo items, each representing an interesting ordering + * of GROUP BY keys. Each item stores pathkeys and clauses in matching order. + * + * The function considers (and keeps) multiple group by orderings: + * + * - the original ordering, as specified by the GROUP BY clause + * + * - GROUP BY keys reordered to minimize the sort cost + * + * - GROUP BY keys reordered to match path ordering (as much as possible), with + * the tail reordered to minimize the sort cost + * + * - GROUP BY keys to match target ORDER BY clause (as much as possible), with + * the tail reordered to minimize the sort cost + * + * There are other potentially interesting orderings (e.g. it might be best to + * match the first ORDER BY key, order the remaining keys differently and then + * rely on the incremental sort to fix this), but we ignore those for now. To + * make this work we'd have to pretty much generate all possible permutations. + */ +List * +get_useful_group_keys_orderings(PlannerInfo *root, double nrows, + List *path_pathkeys, + List *group_pathkeys, List *group_clauses) +{ + Query *parse = root->parse; + List *infos = NIL; + PathKeyInfo *info; + int n_preordered = 0; + + List *pathkeys = group_pathkeys; + List *clauses = group_clauses; + + /* always return at least the original pathkeys/clauses */ + info = makeNode(PathKeyInfo); + info->pathkeys = pathkeys; + info->clauses = clauses; + + infos = lappend(infos, info); + + /* + * Should we try generating alternative orderings of the group keys? If + * not, we produce only the order specified in the query, i.e. the + * optimization is effectively disabled. + */ + if (!enable_group_by_reordering) + return infos; + + /* for grouping sets we can't do any reordering */ + if (parse->groupingSets) + return infos; + + /* + * Try reordering pathkeys to minimize the sort cost, ignoring both the + * target ordering (ORDER BY) and ordering of the input path. + */ + if (get_cheapest_group_keys_order(root, nrows, &pathkeys, &clauses, + n_preordered)) + { + info = makeNode(PathKeyInfo); + info->pathkeys = pathkeys; + info->clauses = clauses; + + infos = lappend(infos, info); + } + + /* + * If the path is sorted in some way, try reordering the group keys to + * match as much of the ordering as possible - we get this sort for free + * (mostly). + * + * We must not do this when there are no grouping sets, because those use + * more complex logic to decide the ordering. + * + * XXX Isn't this somewhat redundant with presorted_keys? Actually, it's + * more a complement, because it allows benefiting from incremental sort + * as much as possible. + * + * XXX This does nothing if (n_preordered == 0). We shouldn't create the + * info in this case. + */ + if (path_pathkeys) + { + n_preordered = group_keys_reorder_by_pathkeys(path_pathkeys, + &pathkeys, + &clauses); + + /* reorder the tail to minimize sort cost */ + get_cheapest_group_keys_order(root, nrows, &pathkeys, &clauses, + n_preordered); + + /* + * reorder the tail to minimize sort cost + * + * XXX Ignore the return value - there may be nothing to reorder, in + * which case get_cheapest_group_keys_order returns false. But we + * still want to keep the keys reordered to path_pathkeys. + */ + info = makeNode(PathKeyInfo); + info->pathkeys = pathkeys; + info->clauses = clauses; + + infos = lappend(infos, info); + } + + /* + * Try reordering pathkeys to minimize the sort cost (this time consider + * the ORDER BY clause, but only if set debug_group_by_match_order_by). + */ + if (root->sort_pathkeys) + { + n_preordered = group_keys_reorder_by_pathkeys(root->sort_pathkeys, + &pathkeys, + &clauses); + + /* + * reorder the tail to minimize sort cost + * + * XXX Ignore the return value - there may be nothing to reorder, in + * which case get_cheapest_group_keys_order returns false. But we + * still want to keep the keys reordered to sort_pathkeys. + */ + get_cheapest_group_keys_order(root, nrows, &pathkeys, &clauses, + n_preordered); + + /* keep the group keys reordered to match ordering of input path */ + info = makeNode(PathKeyInfo); + info->pathkeys = pathkeys; + info->clauses = clauses; + + infos = lappend(infos, info); + } + + return infos; +} + /* * pathkeys_count_contained_in * Same as pathkeys_contained_in, but also sets length of longest @@ -1845,7 +2373,7 @@ right_merge_direction(PlannerInfo *root, PathKey *pathkey) * ordering. Thus we return 0, if no valuable keys are found, or the number * of leading keys shared by the list and the requested ordering.. */ -static int +int pathkeys_useful_for_ordering(PlannerInfo *root, List *pathkeys) { int n_common_pathkeys; @@ -1862,6 +2390,54 @@ pathkeys_useful_for_ordering(PlannerInfo *root, List *pathkeys) return n_common_pathkeys; } +/* + * pathkeys_useful_for_grouping + * Count the number of pathkeys that are useful for grouping (instead of + * explicit sort) + * + * Group pathkeys could be reordered to benefit from the ordering. The + * ordering may not be "complete" and may require incremental sort, but that's + * fine. So we simply count prefix pathkeys with a matching group key, and + * stop once we find the first pathkey without a match. + * + * So e.g. with pathkeys (a,b,c) and group keys (a,b,e) this determines (a,b) + * pathkeys are useful for grouping, and we might do incremental sort to get + * path ordered by (a,b,e). + * + * This logic is necessary to retain paths with ordering not matching grouping + * keys directly, without the reordering. + * + * Returns the length of pathkey prefix with matching group keys. + */ +static int +pathkeys_useful_for_grouping(PlannerInfo *root, List *pathkeys) +{ + ListCell *key; + int n = 0; + + /* no special ordering requested for grouping */ + if (root->group_pathkeys == NIL) + return 0; + + /* unordered path */ + if (pathkeys == NIL) + return 0; + + /* walk the pathkeys and search for matching group key */ + foreach(key, pathkeys) + { + PathKey *pathkey = (PathKey *) lfirst(key); + + /* no matching group key, we're done */ + if (!list_member_ptr(root->group_pathkeys, pathkey)) + break; + + n++; + } + + return n; +} + /* * truncate_useless_pathkeys * Shorten the given pathkey list to just the useful pathkeys. @@ -1876,6 +2452,9 @@ truncate_useless_pathkeys(PlannerInfo *root, nuseful = pathkeys_useful_for_merging(root, rel, pathkeys); nuseful2 = pathkeys_useful_for_ordering(root, pathkeys); + if (nuseful2 > nuseful) + nuseful = nuseful2; + nuseful2 = pathkeys_useful_for_grouping(root, pathkeys); if (nuseful2 > nuseful) nuseful = nuseful2; @@ -1911,6 +2490,8 @@ has_useful_pathkeys(PlannerInfo *root, RelOptInfo *rel) { if (rel->joininfo != NIL || rel->has_eclass_joins) return true; /* might be able to use pathkeys for merging */ + if (root->group_pathkeys != NIL) + return true; /* might be able to use pathkeys for grouping */ if (root->query_pathkeys != NIL) return true; /* might be able to use them for ordering */ return false; /* definitely useless */ diff --git a/src/backend/optimizer/plan/analyzejoins.c b/src/backend/optimizer/plan/analyzejoins.c index 34efeee93f9..210d31cc992 100644 --- a/src/backend/optimizer/plan/analyzejoins.c +++ b/src/backend/optimizer/plan/analyzejoins.c @@ -22,6 +22,7 @@ */ #include "postgres.h" +#include "catalog/pg_class.h" #include "nodes/nodeFuncs.h" #include "optimizer/clauses.h" #include "optimizer/joininfo.h" @@ -29,8 +30,10 @@ #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/planmain.h" +#include "optimizer/restrictinfo.h" #include "optimizer/tlist.h" #include "utils/lsyscache.h" +#include "utils/memutils.h" /* local functions */ static bool join_is_removable(PlannerInfo *root, SpecialJoinInfo *sjinfo); @@ -39,15 +42,17 @@ static void remove_rel_from_query(PlannerInfo *root, int relid, static List *remove_rel_from_joinlist(List *joinlist, int relid, int *nremoved); static bool rel_supports_distinctness(PlannerInfo *root, RelOptInfo *rel); static bool rel_is_distinct_for(PlannerInfo *root, RelOptInfo *rel, - List *clause_list); + List *clause_list, UniqueIndexInfo **info); static Oid distinct_col_search(int colno, List *colnos, List *opids); static bool is_innerrel_unique_for(PlannerInfo *root, Relids joinrelids, Relids outerrelids, RelOptInfo *innerrel, JoinType jointype, - List *restrictlist); + List *restrictlist, + UniqueIndexInfo **info); +static void change_rinfo(RestrictInfo* rinfo, Index from, Index to); /* * remove_useless_joins @@ -58,7 +63,7 @@ static bool is_innerrel_unique_for(PlannerInfo *root, * data structures that have to be updated are accessible via "root". */ List * -remove_useless_joins(PlannerInfo *root, List *joinlist) +remove_useless_left_joins(PlannerInfo *root, List *joinlist) { ListCell *lc; @@ -161,7 +166,6 @@ join_is_removable(PlannerInfo *root, SpecialJoinInfo *sjinfo) int innerrelid; RelOptInfo *innerrel; Relids joinrelids; - List *clause_list = NIL; ListCell *l; int attroff; @@ -245,67 +249,24 @@ join_is_removable(PlannerInfo *root, SpecialJoinInfo *sjinfo) } /* - * Search for mergejoinable clauses that constrain the inner rel against - * either the outer rel or a pseudoconstant. If an operator is - * mergejoinable then it behaves like equality for some btree opclass, so - * it's what we want. The mergejoinability test also eliminates clauses - * containing volatile functions, which we couldn't depend on. + * Check for pushed-down clauses referencing the inner rel. If there is + * such a clause then join removal has to be disallowed. We have to + * check this despite the previous attr_needed checks because of the + * possibility of pushed-down clauses referencing the rel. */ foreach(l, innerrel->joininfo) { RestrictInfo *restrictinfo = (RestrictInfo *) lfirst(l); - - /* - * If it's not a join clause for this outer join, we can't use it. - * Note that if the clause is pushed-down, then it is logically from - * above the outer join, even if it references no other rels (it might - * be from WHERE, for example). - */ - if (RINFO_IS_PUSHED_DOWN(restrictinfo, joinrelids)) - { - /* - * If such a clause actually references the inner rel then join - * removal has to be disallowed. We have to check this despite - * the previous attr_needed checks because of the possibility of - * pushed-down clauses referencing the rel. - */ - if (bms_is_member(innerrelid, restrictinfo->clause_relids)) + if (RINFO_IS_PUSHED_DOWN(restrictinfo, joinrelids) + && bms_is_member(innerrel->relid, restrictinfo->clause_relids)) return false; - continue; /* else, ignore; not useful here */ - } - - /* Ignore if it's not a mergejoinable clause */ - if (!restrictinfo->can_join || - restrictinfo->mergeopfamilies == NIL) - continue; /* not mergejoinable */ - - /* - * Check if clause has the form "outer op inner" or "inner op outer", - * and if so mark which side is inner. - */ - if (!clause_sides_match_join(restrictinfo, sjinfo->min_lefthand, - innerrel->relids)) - continue; /* no good for these input relations */ - - /* OK, add to list */ - clause_list = lappend(clause_list, restrictinfo); } - /* - * Now that we have the relevant equality join clauses, try to prove the - * innerrel distinct. - */ - if (rel_is_distinct_for(root, innerrel, clause_list)) - return true; - - /* - * Some day it would be nice to check for other methods of establishing - * distinctness. - */ - return false; + return is_innerrel_unique_for(root, joinrelids, sjinfo->min_lefthand, + innerrel, sjinfo->jointype, innerrel->joininfo, + NULL /*unique_index*/); } - /* * Remove the target relid from the planner's data structures, having * determined that there is no need to include it in the query. @@ -569,7 +530,7 @@ reduce_unique_semijoins(PlannerInfo *root) /* Test whether the innerrel is unique for those clauses. */ if (!innerrel_is_unique(root, joinrelids, sjinfo->min_lefthand, innerrel, - JOIN_SEMI, restrictlist, true)) + JOIN_SEMI, restrictlist, true, NULL /*index_info*/)) continue; /* OK, remove the SpecialJoinInfo from the list. */ @@ -643,9 +604,13 @@ rel_supports_distinctness(PlannerInfo *root, RelOptInfo *rel) * Note that the passed-in clause_list may be destructively modified! This * is OK for current uses, because the clause_list is built by the caller for * the sole purpose of passing to this function. + * + * If unique_index is not null, it is set to point to the index that guarantees + * uniqueness for a base relation. */ static bool -rel_is_distinct_for(PlannerInfo *root, RelOptInfo *rel, List *clause_list) +rel_is_distinct_for(PlannerInfo *root, RelOptInfo *rel, List *clause_list, + UniqueIndexInfo **index_info) { /* * We could skip a couple of tests here if we assume all callers checked @@ -661,8 +626,8 @@ rel_is_distinct_for(PlannerInfo *root, RelOptInfo *rel, List *clause_list) * relation_has_unique_index_for automatically adds any usable * restriction clauses for the rel, so we needn't do that here. */ - if (relation_has_unique_index_for(root, rel, clause_list, NIL, NIL)) - return true; + return relation_has_unique_index_for(root, rel, clause_list, NIL, NIL, + index_info); } else if (rel->rtekind == RTE_SUBQUERY) { @@ -966,6 +931,10 @@ distinct_col_search(int colno, List *colnos, List *opids) * heuristic about whether to cache negative answers; it should be "true" * if making an inquiry that is not part of the normal bottom-up join search * sequence. + * + * If index_info_out is not null, it is set to point to a new UniqueIndexInfo + * allocated in root memory context, that describes the index that guarantees + * uniqueness. */ bool innerrel_is_unique(PlannerInfo *root, @@ -974,12 +943,23 @@ innerrel_is_unique(PlannerInfo *root, RelOptInfo *innerrel, JoinType jointype, List *restrictlist, - bool force_cache) + bool force_cache, + UniqueIndexInfo **index_info_out) { MemoryContext old_context; ListCell *lc; + UniqueIndexInfo *index_info; - /* Certainly can't prove uniqueness when there are no joinclauses */ + if (index_info_out) + *index_info_out = NULL; + + /* + * It is possible to prove uniqueness even in the absence of joinclauses, + * just from baserestrictinfos alone. However, in these cases the inner + * relation returns one row at most, so join removal won't give much + * benefit. It seems better to save some planning time by ignoring these + * cases. + */ if (restrictlist == NIL) return false; @@ -999,10 +979,14 @@ innerrel_is_unique(PlannerInfo *root, */ foreach(lc, innerrel->unique_for_rels) { - Relids unique_for_rels = (Relids) lfirst(lc); + Relids unique_for_rels = (Relids) linitial(lfirst(lc)); if (bms_is_subset(unique_for_rels, outerrelids)) + { + if (index_info_out) + *index_info_out = lsecond(lfirst(lc)); return true; /* Success! */ + } } /* @@ -1019,7 +1003,7 @@ innerrel_is_unique(PlannerInfo *root, /* No cached information, so try to make the proof. */ if (is_innerrel_unique_for(root, joinrelids, outerrelids, innerrel, - jointype, restrictlist)) + jointype, restrictlist, &index_info)) { /* * Cache the positive result for future probes, being sure to keep it @@ -1033,9 +1017,12 @@ innerrel_is_unique(PlannerInfo *root, */ old_context = MemoryContextSwitchTo(root->planner_cxt); innerrel->unique_for_rels = lappend(innerrel->unique_for_rels, - bms_copy(outerrelids)); + list_make2(bms_copy(outerrelids), index_info)); MemoryContextSwitchTo(old_context); + if (index_info_out) + *index_info_out = index_info; + return true; /* Success! */ } else @@ -1081,7 +1068,8 @@ is_innerrel_unique_for(PlannerInfo *root, Relids outerrelids, RelOptInfo *innerrel, JoinType jointype, - List *restrictlist) + List *restrictlist, + UniqueIndexInfo **index_info) { List *clause_list = NIL; ListCell *lc; @@ -1123,5 +1111,1056 @@ is_innerrel_unique_for(PlannerInfo *root, } /* Let rel_is_distinct_for() do the hard work */ - return rel_is_distinct_for(root, innerrel, clause_list); + return rel_is_distinct_for(root, innerrel, clause_list, index_info); +} + +typedef struct +{ + Index oldRelid; + Index newRelid; +} ChangeVarnoContext; + +static bool +change_varno_walker(Node *node, ChangeVarnoContext *context) +{ + if (node == NULL) + return false; + + if (IsA(node, Var) && ((Var *) node)->varno == context->oldRelid) + { + ((Var *) node)->varno = context->newRelid; + ((Var *) node)->varnosyn = context->newRelid; + return false; + } + if (IsA(node, RestrictInfo)) + { + change_rinfo((RestrictInfo*)node, context->oldRelid, context->newRelid); + return false; + } + + return expression_tree_walker(node, change_varno_walker, context); +} + +/* + * For all Vars in the expression that have varno = oldRelid, set + * varno = newRelid. + */ +static void +change_varno(Expr *expr, Index oldRelid, Index newRelid) +{ + ChangeVarnoContext context; + context.oldRelid = oldRelid; + context.newRelid = newRelid; + change_varno_walker((Node *) expr, &context); +} + +/* + * Substitute newId for oldId in relids. + */ +static void +change_relid(Relids *relids, Index oldId, Index newId) +{ + if (bms_is_member(oldId, *relids)) + *relids = bms_add_member(bms_del_member(bms_copy(*relids), oldId), newId); +} + +/* + * Substitute newId for oldId in field of RestrictInfo + */ +static void +change_rinfo(RestrictInfo* rinfo, Index from, Index to) +{ + bool is_req_equal = + (rinfo->required_relids == rinfo->clause_relids) ? true : false; + + /* + * At the required_relids initialization stage we do not create independent + * bms structure. The field is a link to clause_relids. During the call + * of change_relid() routine clause_relids can change memory location. + */ + change_relid(&rinfo->clause_relids, from, to); + if (is_req_equal) + rinfo->required_relids = rinfo->clause_relids; + else + change_relid(&rinfo->required_relids, from, to); + + change_varno(rinfo->clause, from, to); + change_varno(rinfo->orclause, from, to); + change_relid(&rinfo->left_relids, from, to); + change_relid(&rinfo->right_relids, from, to); + change_relid(&rinfo->outer_relids, from, to); + change_relid(&rinfo->nullable_relids, from, to); +} + +/* + * Update EC members to point to the remaining relation instead of the removed + * one, removing duplicates. + */ +static void +update_ec_members(EquivalenceClass *ec, Index toRemove, Index toKeep) +{ + ListCell *cell = NULL; + ListCell *next; + +restart: + next = list_head(ec->ec_members); + + while (next) + { + EquivalenceMember *em; + ListCell *otherCell; + + cell = next; + next = lnext(ec->ec_members, next); + + em = lfirst(cell); + + if (!bms_is_member(toRemove, em->em_relids)) + continue; + + change_relid(&em->em_relids, toRemove, toKeep); + /* We only process inner joins */ +// Assert(bms_is_empty(em->em_nullable_relids)); + change_varno(em->em_expr, toRemove, toKeep); + + /* + * After we switched the equivalence member to the remaining relation, + * check that it is not the same as the existing member, and if it + * is, delete it. + */ + foreach (otherCell, ec->ec_members) + { + EquivalenceMember *other; + + if (otherCell == cell) + continue; + + other = castNode(EquivalenceMember, lfirst(otherCell)); + if (equal(other->em_expr, em->em_expr)) + { + ec->ec_members = list_delete_cell(ec->ec_members, cell); + goto restart; + } + } + } +} + +/* + * Update EC sources to point to the remaining relation instead of the + * removed one. + */ +static void +update_ec_sources(List **sources, Index toRemove, Index toKeep) +{ + ListCell *cell = NULL; + ListCell *next; + +restart: + next = list_head(*sources); + + while (next) + { + RestrictInfo *rinfo; + ListCell *otherCell; + + cell = next; + next = lnext(*sources, next); + + rinfo = castNode(RestrictInfo, lfirst(cell)); + + if (!bms_is_member(toRemove, rinfo->required_relids)) + continue; + + change_varno(rinfo->clause, toRemove, toKeep); + + /* + * After switching the clause to the remaining relation, check it + * for redundancy with existing ones. We don't have to check for + * redundancy with derived clauses, because we've just deleted them. + */ + foreach (otherCell, *sources) + { + RestrictInfo *other; + + if (otherCell == cell) + continue; + + other = castNode(RestrictInfo, lfirst(otherCell)); + + if (equal(rinfo->clause, other->clause)) + { + *sources = list_delete_cell(*sources, cell); + cell = NULL; + break; + } + } + + if (otherCell == NULL) + { + /* We will keep this RestrictInfo, correct its relids. */ + change_relid(&rinfo->required_relids, toRemove, toKeep); + change_relid(&rinfo->left_relids, toRemove, toKeep); + change_relid(&rinfo->right_relids, toRemove, toKeep); + change_relid(&rinfo->clause_relids, toRemove, toKeep); + } + + if (cell == NULL) + goto restart; + } +} + +/* + * Scratch space for the unique self join removal code. + */ +typedef struct +{ + PlannerInfo *root; + + /* Temporary array for relation ids. */ + Index *relids; + + /* + * Array of Relids, one for each relation, indexed by relation id. + * Each element is a set of relation ids with which this relation + * has a special join. + */ + Relids *special_join_rels; + + /* Array of row marks indexed by relid. */ + PlanRowMark **row_marks; + + /* Bitmapset for join relids that is used to avoid reallocation. */ + Relids joinrelids; + + /* + * Top-level targetlist of the query. We have to update any references + * it has to the relations we remove. + */ + List *targetlist; +} UsjScratch; + + +/* + * Remove a relation after we have proven that it participates only in an + * unneeded unique self join. + * + * The joinclauses list is destructively changed. + */ +static void +remove_self_join_rel(UsjScratch *scratch, Relids joinrelids, List *joinclauses, + RelOptInfo *toKeep, RelOptInfo *toRemove) +{ + PlannerInfo *root = scratch->root; + ListCell *cell; + int i; + + /* Merge EC indexes */ + toKeep->eclass_indexes = bms_add_members(toRemove->eclass_indexes, + toKeep->eclass_indexes); + + /* + * Transfer join and restriction clauses from the removed relation to the + * remaining one. We change the Vars of the clause to point to the remaining + * relation instead of the removed one. The clauses that require a subset of + * joinrelids become restriction clauses of the remaining relation, and + * others remain join clauses. We append them to baserestrictinfo and + * joininfo respectively, trying not to introduce duplicates. + * + * We also have to process the 'joinclauses' list here, because it contains + * EC-derived join clauses which must become filter clauses. It is not enough + * to just correct the ECs, because the EC-derived restrictions are generated + * before join removal (see generate_base_implied_equalities). + */ + /* Replace removed relation in joininfo */ + foreach(cell, toRemove->joininfo) + { + RestrictInfo *rinfo = lfirst_node(RestrictInfo, cell); + + change_rinfo(rinfo, toRemove->relid, toKeep->relid); + + /* + * If this clause is a mergejoinable equality clause that compares a + * variable to itself, i.e., has the form of "X=X", replace it with + * null test. + */ + if (rinfo->mergeopfamilies && IsA(rinfo->clause, OpExpr)) + { + Expr *leftOp, *rightOp; + + leftOp = (Expr *) get_leftop(rinfo->clause); + rightOp = (Expr *) get_rightop(rinfo->clause); + + if (leftOp != NULL && equal(leftOp, rightOp)) + { + NullTest *nullTest = makeNode(NullTest); + nullTest->arg = leftOp; + nullTest->nulltesttype = IS_NOT_NULL; + nullTest->argisrow = false; + nullTest->location = -1; + rinfo->clause = (Expr*)nullTest; + } + } + } + + foreach(cell, joinclauses) + { + RestrictInfo *rinfo = lfirst_node(RestrictInfo, cell); + + change_rinfo(rinfo, toRemove->relid, toKeep->relid); + + /* + * If this clause is a mergejoinable equality clause that compares a + * variable to itself, i.e., has the form of "X=X", replace it with + * null test. + */ + if (rinfo->mergeopfamilies && IsA(rinfo->clause, OpExpr)) + { + Expr *leftOp, *rightOp; + + leftOp = (Expr *) get_leftop(rinfo->clause); + rightOp = (Expr *) get_rightop(rinfo->clause); + + if (leftOp != NULL && equal(leftOp, rightOp)) + { + NullTest *nullTest = makeNode(NullTest); + nullTest->arg = leftOp; + nullTest->nulltesttype = IS_NOT_NULL; + nullTest->argisrow = false; + nullTest->location = -1; + rinfo->clause = (Expr*)nullTest; + toKeep->baserestrictinfo = lappend(toKeep->baserestrictinfo, rinfo); + //toKeep->joininfo = lappend(toKeep->joininfo, rinfo); + } + } + } + + + /* Tranfer removed relations clauses to kept relation */ + foreach(cell, toRemove->baserestrictinfo) + { + ListCell *otherCell; + RestrictInfo *rinfo = lfirst_node(RestrictInfo, cell); + List **target = &toKeep->baserestrictinfo; + + /* + * Replace the references to the removed relation with references to + * the remaining one. We won't necessarily add this clause, because + * it may be already present in the joininfo or baserestrictinfo. + * Still, we have to switch it to point to the remaining relation. + * This is important for join clauses that reference both relations, + * because they are included in both joininfos. + */ + change_rinfo(rinfo, toRemove->relid, toKeep->relid); + + /* + * Don't add the clause if it is already present in the list, or + * derived from the same equivalence class, or is the same as another + * clause. + */ + foreach (otherCell, *target) + { + RestrictInfo *other = lfirst(otherCell); + if (other == rinfo + || (rinfo->parent_ec != NULL + && other->parent_ec == rinfo->parent_ec) + || equal(rinfo->clause, other->clause)) + { + break; + } + } + + if (otherCell != NULL) + continue; + + *target = lappend(*target, rinfo); + } + + /* Tranfer removed relations clauses to kept relation */ + foreach(cell, toRemove->joininfo) + { + ListCell *otherCell; + RestrictInfo *rinfo = lfirst_node(RestrictInfo, cell); + List **target = &toKeep->joininfo; + + /* + * Replace the references to the removed relation with references to + * the remaining one. We won't necessarily add this clause, because + * it may be already present in the joininfo or baserestrictinfo. + * Still, we have to switch it to point to the remaining relation. + * This is important for join clauses that reference both relations, + * because they are included in both joininfos. + */ + change_varno(rinfo->clause, toRemove->relid, toKeep->relid); + change_relid(&rinfo->required_relids, toRemove->relid, toKeep->relid); + change_relid(&rinfo->left_relids, toRemove->relid, toKeep->relid); + change_relid(&rinfo->right_relids, toRemove->relid, toKeep->relid); + change_relid(&rinfo->clause_relids, toRemove->relid, toKeep->relid); + + /* + * Don't add the clause if it is already present in the list, or + * derived from the same equivalence class, or is the same as another + * clause. + */ + foreach (otherCell, *target) + { + RestrictInfo *other = lfirst(otherCell); + if (other == rinfo + || (rinfo->parent_ec != NULL + && other->parent_ec == rinfo->parent_ec) + || equal(rinfo->clause, other->clause)) + { + break; + } + } + + if (otherCell != NULL) + continue; + + /* + * If this clause is a mergejoinable equality clause that compares a + * variable to itself, i.e., has the form of "X=X", replace it with + * null test. + */ + if (rinfo->mergeopfamilies && IsA(rinfo->clause, OpExpr)) + { + Expr *leftOp, *rightOp; + + Assert(IsA(rinfo->clause, OpExpr)); + + leftOp = (Expr *) get_leftop(rinfo->clause); + rightOp = (Expr *) get_rightop(rinfo->clause); + + if (leftOp != NULL && equal(leftOp, rightOp)) + { + NullTest *nullTest = makeNode(NullTest); + nullTest->arg = leftOp; + nullTest->nulltesttype = IS_NOT_NULL; + nullTest->argisrow = false; + nullTest->location = -1; + rinfo->clause = (Expr*)nullTest; + } + } + + *target = lappend(*target, rinfo); + } + + /* + * Transfer the targetlist and attr_needed flags. + */ + Assert(toRemove->reltarget->sortgrouprefs == 0); + + foreach (cell, toRemove->reltarget->exprs) + { + Expr *node = lfirst(cell); + change_varno(node, toRemove->relid, toKeep->relid); + if (!list_member(toKeep->reltarget->exprs, node)) + toKeep->reltarget->exprs = lappend(toKeep->reltarget->exprs, + node); + } + + for (i = toKeep->min_attr; i <= toKeep->max_attr; i++) + { + int attno = i - toKeep->min_attr; + toKeep->attr_needed[attno] = bms_add_members(toKeep->attr_needed[attno], + toRemove->attr_needed[attno]); + } + + /* + * If the removed relation has a row mark, transfer it to the remaining one. + * + * If both rels have row marks, just keep the one corresponding to the + * remaining relation, because we verified earlier that they have the same + * strength. + * + * Also make sure that the scratch->row_marks cache is up to date, because + * we are going to use it for further join removals. + */ + if (scratch->row_marks[toRemove->relid]) + { + PlanRowMark **markToRemove = &scratch->row_marks[toRemove->relid]; + PlanRowMark **markToKeep = &scratch->row_marks[toKeep->relid]; + + if (*markToKeep) + { + Assert((*markToKeep)->markType == (*markToRemove)->markType); + + root->rowMarks = list_delete_ptr(root->rowMarks, *markToKeep); + *markToKeep = NULL; + } + else + { + *markToKeep = *markToRemove; + *markToRemove = NULL; + + /* Shouldn't have inheritance children here. */ + Assert((*markToKeep)->rti == (*markToKeep)->prti); + + (*markToKeep)->rti = toKeep->relid; + (*markToKeep)->prti = toKeep->relid; + } + } + + /* + * Likewise replace references in SpecialJoinInfo data structures. + * + * This is relevant in case the join we're deleting is nested inside + * some special joins: the upper joins' relid sets have to be adjusted. + */ + foreach (cell, root->join_info_list) + { + SpecialJoinInfo *sjinfo = (SpecialJoinInfo *) lfirst(cell); + + change_relid(&sjinfo->min_lefthand, toRemove->relid, toKeep->relid); + change_relid(&sjinfo->min_righthand, toRemove->relid, toKeep->relid); + change_relid(&sjinfo->syn_lefthand, toRemove->relid, toKeep->relid); + change_relid(&sjinfo->syn_righthand, toRemove->relid, toKeep->relid); + change_varno((Expr*)sjinfo->semi_rhs_exprs, toRemove->relid, toKeep->relid); + } + + /* + * Likewise update references in PlaceHolderVar data structures. + */ + foreach(cell, root->placeholder_list) + { + PlaceHolderInfo *phinfo = (PlaceHolderInfo *) lfirst(cell); + + /* + * We are at an inner join of two base relations. A placeholder + * can't be needed here or evaluated here. + */ + Assert(!bms_is_subset(phinfo->ph_eval_at, joinrelids)); + Assert(!bms_is_subset(phinfo->ph_needed, joinrelids)); + + change_relid(&phinfo->ph_eval_at, toRemove->relid, toKeep->relid); + change_relid(&phinfo->ph_needed, toRemove->relid, toKeep->relid); + change_relid(&phinfo->ph_lateral, toRemove->relid, toKeep->relid); + change_relid(&phinfo->ph_var->phrels, toRemove->relid, toKeep->relid); + change_varno((Expr*)phinfo->ph_var, toRemove->relid, toKeep->relid); + } + + /* + * Update the equivalence classes that reference the removed relations. + */ + foreach(cell, root->eq_classes) + { + EquivalenceClass *ec = lfirst(cell); + + if (!bms_is_member(toRemove->relid, ec->ec_relids)) + { + /* + * This EC doesn't reference the removed relation, + * nothing to be done for it. + */ + continue; + } + + /* + * Update the EC members to reference the remaining relation + * instead of the removed one. + */ + update_ec_members(ec, toRemove->relid, toKeep->relid); + change_relid(&ec->ec_relids, toRemove->relid, toKeep->relid); + + /* + * We will now update source and derived clauses of the EC. + * + * Restriction clauses for base relations are already distributed + * to the respective baserestrictinfo lists (see + * generate_implied_equalities). The above code has already + * processed this list, and updated these clauses to reference + * the remaining relation, so we can skip them here based on their + * relids. + * + * Likewise, we have already processed the join clauses that join + * the removed relation to the remaining one. + * + * Finally, there are join clauses that join the removed relation + * to some third relation. We could delete just delete them and + * generate on demand, but sometimes we can't do this because there + * is no suitable equality operator (see the handling of ec_broken). + * In such cases we are going to use the source clauses, so we have + * to correct them too. + * + * Derived clauses can be generated again, so it is simpler to just + * delete them. + */ + list_free(ec->ec_derives); + ec->ec_derives = NIL; + update_ec_sources(&ec->ec_sources, toRemove->relid, toKeep->relid); + } + + /* + * Mark the rel as "dead" to show it is no longer part of the join tree. + * (Removing it from the baserel array altogether seems too risky.) + */ + toRemove->reloptkind = RELOPT_DEADREL; + + /* + * Update references to the removed relation from other baserels. + */ + for (i = 1; i < root->simple_rel_array_size; i++) + { + RelOptInfo *otherrel = root->simple_rel_array[i]; + int attroff; + + /* no point in processing target rel itself */ + if (i == toRemove->relid) + continue; + + /* there may be empty slots corresponding to non-baserel RTEs */ + if (otherrel == NULL) + continue; + + Assert(otherrel->relid == i); /* sanity check on array */ + + /* Update attr_needed arrays. */ + for (attroff = otherrel->max_attr - otherrel->min_attr; + attroff >= 0; attroff--) + { + change_relid(&otherrel->attr_needed[attroff], toRemove->relid, + toKeep->relid); + } + + /* Update lateral references. */ + change_varno((Expr*)otherrel->lateral_vars, toRemove->relid, toKeep->relid); + } + + /* + * Change varno in some special cases with non-trivial RangeTblEntry + */ + foreach(cell, root->parse->rtable) + { + RangeTblEntry *rte = lfirst(cell); + + switch(rte->rtekind) + { + case RTE_FUNCTION: + change_varno((Expr*)rte->functions, toRemove->relid, toKeep->relid); + break; + case RTE_TABLEFUNC: + change_varno((Expr*)rte->tablefunc, toRemove->relid, toKeep->relid); + break; + case RTE_VALUES: + change_varno((Expr*)rte->values_lists, toRemove->relid, toKeep->relid); + break; + default: + /* no op */ + break; + } + } +} + +/* + * Test whether the relations are joined on the same unique column. + */ +static bool +is_unique_self_join(PlannerInfo *root, Relids joinrelids, RelOptInfo *outer, + RelOptInfo *inner, List *restrictlist) +{ + UniqueIndexInfo *outeridx = NULL; + UniqueIndexInfo *inneridx = NULL; + ListCell *outerCell, *innerCell; + + innerrel_is_unique(root, joinrelids, inner->relids, + outer, JOIN_INNER, list_concat(list_concat_unique(list_copy(outer->joininfo), restrictlist), + outer->baserestrictinfo), true, &outeridx); + if (!outeridx) + return false; + + innerrel_is_unique(root, joinrelids, outer->relids, + inner, JOIN_INNER, list_concat(list_concat_unique(list_copy(inner->joininfo), restrictlist), + inner->baserestrictinfo), true, &inneridx); + if (!inneridx) + return false; + + /* We must have the same unique index for both relations. */ + if (outeridx->index->indexoid != inneridx->index->indexoid) + return false; + + if (restrictlist != NULL && IsA(((RestrictInfo*)linitial(restrictlist))->clause, Const)) + return false; + + /* + * The clauses that make a relation unique must be the same for both + * relations, or else we won't match the same row on each side of join. + * + * The lists of matching clauses are ordered as the index columns, so we + * just compare the list elements one by one. The varnos are different, + * so we copy the clauses and replace all mentions of outer varno with the + * inner, so that we can use equal(). + */ + forboth(innerCell, inneridx->clauses, outerCell, outeridx->clauses) + { + Expr *innerExpr = copyObject(castNode(RestrictInfo, lfirst(innerCell))->clause); + Expr *outerExpr = copyObject(castNode(RestrictInfo, lfirst(outerCell))->clause); + change_varno(outerExpr, outer->relid, inner->relid); + change_varno(innerExpr, outer->relid, inner->relid); + if (!equal(outerExpr, innerExpr)) + { + pfree(outerExpr); + pfree(innerExpr); + return false; + } + pfree(outerExpr); + pfree(innerExpr); + } + + return true; +} + +static bool +check_selfjoin(List* join_info_list, Index *relids, int i, int j) +{ + ListCell *cell; + + foreach(cell, join_info_list) + { + SpecialJoinInfo *info = (SpecialJoinInfo *) lfirst(cell); + + if (bms_is_member(relids[i], info->syn_lefthand) && + !bms_is_member(relids[j], info->syn_lefthand)) + return true; + + if (bms_is_member(relids[i], info->syn_righthand) && + !bms_is_member(relids[j], info->syn_righthand)) + return true; + + if (bms_is_member(relids[j], info->syn_lefthand) && + !bms_is_member(relids[i], info->syn_lefthand)) + return true; + + if (bms_is_member(relids[j], info->syn_righthand) && + !bms_is_member(relids[i], info->syn_righthand)) + return true; + } + + return false; +} + +/* + * Find and remove unique self joins in a group of base relations that have + * the same Oid. + * + * Returns a set of relids that were removed. + */ +static Relids +remove_self_joins_one_group(UsjScratch *scratch, Index *relids, int n) +{ + PlannerInfo *root = scratch->root; + Relids joinrelids = scratch->joinrelids; + Relids result = NULL; + int i, o; + + if (n < 2) + return NULL; + + for (o = 0; o < n; o++) + { + RelOptInfo *outer = root->simple_rel_array[relids[o]]; + + if (outer->reloptkind == RELOPT_DEADREL) + continue; + + for (i = o + 1; i < n; i++) + { + RelOptInfo *inner = root->simple_rel_array[relids[i]]; + List *restrictlist; + ListCell *cell; + + /* A sanity check: the relations have the same Oid. */ + Assert(root->simple_rte_array[relids[i]]->relid + == root->simple_rte_array[relids[o]]->relid); + + if (inner->reloptkind == RELOPT_DEADREL) + continue; + + /* + * This optimization applies to inner joins only, so skip any relations + * that form a special join. + */ + if (bms_is_member(relids[i], scratch->special_join_rels[relids[o]])) + continue; + + if (check_selfjoin(root->join_info_list, relids, i, o)) + continue; + + /* Reuse joinrelids bitset to avoid reallocation. */ + joinrelids = bms_del_members(joinrelids, joinrelids); + + /* + * We only deal with base rels here, so their relids bitset + * contains only one member -- their relid. + */ + joinrelids = bms_add_member(joinrelids, relids[o]); + joinrelids = bms_add_member(joinrelids, relids[i]); + + /* Is it a unique self join? */ + restrictlist = build_joinrel_restrictlist(root, joinrelids, outer, inner); + if (!is_unique_self_join(root, joinrelids, outer, inner, + restrictlist)) + continue; + + /* + * We can't remove the join if the relations have row marks of + * different strength (e.g. one is locked FOR UPDATE and another + * just has ROW_MARK_REFERENCE for EvalPlanQual rechecking). + */ + if (scratch->row_marks[relids[i]] && scratch->row_marks[relids[o]] + && scratch->row_marks[relids[i]]->markType + != scratch->row_marks[relids[o]]->markType) + { + continue; + } + + foreach(cell, root->placeholder_list) + { + PlaceHolderInfo *phinfo = (PlaceHolderInfo *) lfirst(cell); + + /* there isn't any other place to eval PHV */ + if (bms_is_subset(phinfo->ph_eval_at, joinrelids) || + bms_is_subset(phinfo->ph_needed, joinrelids)) + break; + } + + if (cell) + continue; + + /* + * We can remove either relation, so remove the outer one, + * to simplify this loop. + */ + remove_self_join_rel(scratch, joinrelids, restrictlist, inner, outer); + result = bms_add_member(result, relids[o]); + + /* + * Replace varno in root targetlist and HAVING clause. + */ + change_varno((Expr *) scratch->targetlist, relids[o], relids[i]); + change_varno((Expr *) root->parse->havingQual, relids[o], relids[i]); + + /* We removed the outer relation, try the next one. */ + break; + } + } + + scratch->joinrelids = joinrelids; + return result; +} + +/* + * A qsort comparator to sort the relids by the relation Oid. + */ +static int +compare_rte(const Index *left, const Index *right, PlannerInfo *root) +{ + Oid l = root->simple_rte_array[*left]->relid; + Oid r = root->simple_rte_array[*right]->relid; + return l < r ? 1 : ( l == r ? 0 : -1 ); +} + +/* + * Find and remove unique self joins on a particular level of the join tree. + * + * We sort the relations by Oid and then examine each group with the same Oid. + * If we removed any relation, remove it from joinlist as well. + */ +static void +remove_self_joins_one_level(UsjScratch *scratch, List *joinlist, + Relids *relidsToRemove) +{ + ListCell *cell; + Oid groupOid; + int groupStart; + int i; + int n = 0; + Index *relid_ascending = scratch->relids; + PlannerInfo *root = scratch->root; + + /* + * Collect the ids of base relations at this level of the join tree. + */ + foreach (cell, joinlist) + { + RangeTblEntry *rte; + RelOptInfo *rel; + RangeTblRef *ref = (RangeTblRef *) lfirst(cell); + if (!IsA(ref, RangeTblRef)) + continue; + + rte = root->simple_rte_array[ref->rtindex]; + rel = root->simple_rel_array[ref->rtindex]; + + /* We only care about base relations from which we select something. */ + if (rte->rtekind != RTE_RELATION || rte->relkind != RELKIND_RELATION + || rel == NULL) + { + continue; + } + + /* This optimization won't work for tables that have inheritance children. */ + if (rte->inh) + continue; + + relid_ascending[n++] = ref->rtindex; + + /* Limit the number of joins we process to control the quadratic behavior. */ + if (n > join_collapse_limit) + break; + } + + if (n < 2) + return; + + /* + * Find and process the groups of relations that have same Oid. + */ + qsort_arg(relid_ascending, n, sizeof(*relid_ascending), + (qsort_arg_comparator) compare_rte, root); + groupOid = root->simple_rte_array[relid_ascending[0]]->relid; + groupStart = 0; + for (i = 1; i < n; i++) + { + RangeTblEntry *rte = root->simple_rte_array[relid_ascending[i]]; + Assert(rte->relid != InvalidOid); + if (rte->relid != groupOid) + { + *relidsToRemove = bms_add_members(*relidsToRemove, + remove_self_joins_one_group(scratch, &relid_ascending[groupStart], + i - groupStart)); + groupOid = rte->relid; + groupStart = i; + } + } + Assert(groupOid != InvalidOid); + Assert(groupStart < n); + *relidsToRemove = bms_add_members(*relidsToRemove, + remove_self_joins_one_group(scratch, &relid_ascending[groupStart], + n - groupStart)); + + return; +} + +/* + * Find and remove unique self joins on a single level of a join tree, and + * recurse to handle deeper levels. + */ +static void +remove_self_joins_recurse(UsjScratch *scratch, List *joinlist, + Relids *relidsToRemove) +{ + ListCell *lc; + foreach (lc, joinlist) + { + switch (((Node*) lfirst(lc))->type) + { + case T_List: + remove_self_joins_recurse(scratch, (List *) lfirst(lc), + relidsToRemove); + break; + case T_RangeTblRef: + break; + default: + Assert(false); + } + } + + remove_self_joins_one_level(scratch, joinlist, relidsToRemove); +} + +bool enable_self_join_removal; + +/* + * Find and remove useless self joins. + * + * We search for joins where the same relation is joined to itself on all + * columns of some unique index. If this condition holds, then, for + * each outer row, only one inner row matches, and it is the same row + * of the same relation. This allows us to remove the join and replace + * it with a scan that combines WHERE clauses from both sides. The join + * clauses themselves assume the form of X = X and can be replaced with + * NOT NULL clauses. + * + * For the sake of simplicity, we don't apply this optimization to special + * joins. Here is a list of what we could do in some particular cases: + * 'a a1 semi join a a2': is reduced to inner by reduce_unique_semijoins, + * and then removed normally. + * 'a a1 anti join a a2': could simplify to a scan with 'outer quals AND + * (IS NULL on join columns OR NOT inner quals)'. + * 'a a1 left join a a2': could simplify to a scan like inner, but without + * NOT NULL condtions on join columns. + * 'a a1 left join (a a2 join b)': can't simplify this, because join to b + * can both remove rows and introduce duplicates. + * + * To search for removable joins, we order all the relations on their Oid, + * go over each set with the same Oid, and consider each pair of relations + * in this set. We check that both relation are made unique by the same + * unique index with the same clauses. + * + * To remove the join, we mark one of the participating relation as + * dead, and rewrite all references to it to point to the remaining + * relation. This includes modifying RestrictInfos, EquivalenceClasses and + * EquivalenceMembers. We also have to modify the row marks. The join clauses + * of the removed relation become either restriction or join clauses, based on + * whether they reference any relations not participating in the removed join. + * + * 'targetlist' is the top-level targetlist of query. If it has any references + * to the removed relations, we update them to point to the remaining ones. + */ +void +remove_useless_self_joins(PlannerInfo *root, List **joinlist, List *targetlist) +{ + ListCell *lc; + UsjScratch scratch; + Relids relidsToRemove = NULL; + int nremoved = 0, + relid = -1; + + if (!enable_self_join_removal) + return; + + scratch.root = root; + scratch.relids = palloc(root->simple_rel_array_size * sizeof(Index)); + scratch.special_join_rels = palloc0(root->simple_rel_array_size * sizeof(Relids)); + scratch.row_marks = palloc0(root->simple_rel_array_size * sizeof(PlanRowMark *)); + scratch.joinrelids = NULL; + scratch.targetlist = targetlist; + + /* Find out which relations have special joins to which. */ + foreach(lc, root->join_info_list) + { + SpecialJoinInfo *info = (SpecialJoinInfo *) lfirst(lc); + int bit = -1; + while ((bit = bms_next_member(info->syn_lefthand, bit)) >= 0) + { + RelOptInfo *rel = find_base_rel(root, bit); + scratch.special_join_rels[rel->relid] = + bms_add_members(scratch.special_join_rels[rel->relid], + info->syn_righthand); + } + + bit = -1; + while ((bit = bms_next_member(info->syn_righthand, bit)) >= 0) + { + RelOptInfo *rel = find_base_rel(root, bit); + scratch.special_join_rels[rel->relid] = + bms_add_members(scratch.special_join_rels[rel->relid], + info->syn_lefthand); + } + } + + /* Collect row marks. */ + foreach (lc, root->rowMarks) + { + PlanRowMark *rowMark = (PlanRowMark *) lfirst(lc); + + /* Can't have more than one row mark for a relation. */ + Assert(scratch.row_marks[rowMark->rti] == NULL); + + scratch.row_marks[rowMark->rti] = rowMark; + } + + /* Finally, remove the joins. */ + remove_self_joins_recurse(&scratch, *joinlist, &relidsToRemove); + while ((relid = bms_next_member(relidsToRemove, relid)) >= 0) + *joinlist = remove_rel_from_joinlist(*joinlist, relid, &nremoved); } diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index 1bc59c94578..82130419a11 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -170,7 +170,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); @@ -1225,6 +1224,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)), @@ -1253,7 +1253,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. @@ -1287,11 +1287,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; @@ -4953,7 +4959,8 @@ replace_nestloop_params_mutator(Node *node, PlannerInfo *root) } return expression_tree_mutator(node, replace_nestloop_params_mutator, - (void *) root); + (void *) root, + 0); } /* @@ -5117,7 +5124,7 @@ fix_indexqual_clause(PlannerInfo *root, IndexOptInfo *index, int indexcol, * Most of the code here is just for sanity cross-checking that the given * expression actually matches the index column it's claimed to. */ -static Node * +Node * fix_indexqual_operand(Node *node, IndexOptInfo *index, int indexcol) { Var *result; diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c index 5a1d00662fc..a36d946430a 100644 --- a/src/backend/optimizer/plan/planmain.c +++ b/src/backend/optimizer/plan/planmain.c @@ -218,7 +218,7 @@ query_planner(PlannerInfo *root, * jointree preprocessing, but the necessary information isn't available * until we've built baserel data structures and classified qual clauses. */ - joinlist = remove_useless_joins(root, joinlist); + joinlist = remove_useless_left_joins(root, joinlist); /* * Also, reduce any semijoins with unique inner rels to plain inner joins. @@ -226,6 +226,11 @@ query_planner(PlannerInfo *root, */ reduce_unique_semijoins(root); + /* + * Remove self joins on a unique column. + */ + remove_useless_self_joins(root, &joinlist, root->processed_tlist); + /* * Now distribute "placeholders" to base rels as needed. This has to be * done after join removal because removal could change whether a diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index bd4e4ceeca5..8787f60b354 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -270,11 +270,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; } @@ -2754,8 +2765,9 @@ remove_useless_groupby_columns(PlannerInfo *root) * * In principle it might be interesting to consider other orderings of the * GROUP BY elements, which could match the sort ordering of other - * possible plans (eg an indexscan) and thereby reduce cost. We don't - * bother with that, though. Hashed grouping will frequently win anyway. + * possible plans (eg an indexscan) and thereby reduce cost. However, we + * don't yet have sufficient information to do that here, so that's left until + * later in planning. See get_useful_group_keys_orderings(). * * Note: we need no comparable processing of the distinctClause because * the parser already enforced that that matches ORDER BY. @@ -3562,7 +3574,8 @@ create_degenerate_grouping_paths(PlannerInfo *root, RelOptInfo *input_rel, NULL, 0, false, - -1); + -1, + false); } else { @@ -6229,24 +6242,122 @@ add_paths_to_grouping_rel(PlannerInfo *root, RelOptInfo *input_rel, */ foreach(lc, input_rel->pathlist) { + ListCell *lc2; Path *path = (Path *) lfirst(lc); Path *path_original = path; - bool is_sorted; - int presorted_keys; - is_sorted = pathkeys_count_contained_in(root->group_pathkeys, - path->pathkeys, - &presorted_keys); + List *pathkey_orderings = NIL; + + List *group_pathkeys = root->group_pathkeys; + List *group_clauses = parse->groupClause; - if (path == cheapest_path || is_sorted) + /* generate alternative group orderings that might be useful */ + pathkey_orderings = get_useful_group_keys_orderings(root, + path->rows, + path->pathkeys, + group_pathkeys, + group_clauses); + + Assert(list_length(pathkey_orderings) > 0); + + /* process all potentially interesting grouping reorderings */ + foreach(lc2, pathkey_orderings) { - /* Sort the cheapest-total path if it isn't already sorted */ - if (!is_sorted) - path = (Path *) create_sort_path(root, - grouped_rel, - path, - root->group_pathkeys, - -1.0); + bool is_sorted; + int presorted_keys = 0; + PathKeyInfo *info = (PathKeyInfo *) lfirst(lc2); + + /* restore the path (we replace it in the loop) */ + path = path_original; + + is_sorted = pathkeys_count_contained_in(info->pathkeys, + path->pathkeys, + &presorted_keys); + + if (path == cheapest_path || is_sorted) + { + /* Sort the cheapest-total path if it isn't already sorted */ + if (!is_sorted) + path = (Path *) create_sort_path(root, + grouped_rel, + path, + info->pathkeys, + -1.0); + + /* Now decide what to stick atop it */ + if (parse->groupingSets) + { + consider_groupingsets_paths(root, grouped_rel, + path, true, can_hash, + gd, agg_costs, dNumGroups); + } + else if (parse->hasAggs) + { + /* + * We have aggregation, possibly with plain GROUP BY. + * Make an AggPath. + */ + add_path(grouped_rel, (Path *) + create_agg_path(root, + grouped_rel, + path, + grouped_rel->reltarget, + info->clauses ? AGG_SORTED : AGG_PLAIN, + AGGSPLIT_SIMPLE, + info->clauses, + havingQual, + agg_costs, + dNumGroups)); + } + else if (group_clauses) + { + /* + * We have GROUP BY without aggregation or grouping + * sets. Make a GroupPath. + */ + add_path(grouped_rel, (Path *) + create_group_path(root, + grouped_rel, + path, + info->clauses, + havingQual, + dNumGroups)); + } + else + { + /* Other cases should have been handled above */ + Assert(false); + } + } + + /* + * Now we may consider incremental sort on this path, but only + * when the path is not already sorted and when incremental + * sort is enabled. + */ + if (is_sorted || !enable_incremental_sort) + continue; + + /* Restore the input path (we might have added Sort on top). */ + path = path_original; + + /* no shared prefix, no point in building incremental sort */ + if (presorted_keys == 0) + continue; + + /* + * We should have already excluded pathkeys of length 1 + * because then presorted_keys > 0 would imply is_sorted was + * true. + */ + Assert(list_length(root->group_pathkeys) != 1); + + path = (Path *) create_incremental_sort_path(root, + grouped_rel, + path, + info->pathkeys, + presorted_keys, + -1.0); /* Now decide what to stick atop it */ if (parse->groupingSets) @@ -6266,9 +6377,9 @@ add_paths_to_grouping_rel(PlannerInfo *root, RelOptInfo *input_rel, grouped_rel, path, grouped_rel->reltarget, - parse->groupClause ? AGG_SORTED : AGG_PLAIN, + info->clauses ? AGG_SORTED : AGG_PLAIN, AGGSPLIT_SIMPLE, - parse->groupClause, + info->clauses, havingQual, agg_costs, dNumGroups)); @@ -6283,7 +6394,7 @@ add_paths_to_grouping_rel(PlannerInfo *root, RelOptInfo *input_rel, create_group_path(root, grouped_rel, path, - parse->groupClause, + info->clauses, havingQual, dNumGroups)); } @@ -6293,79 +6404,6 @@ add_paths_to_grouping_rel(PlannerInfo *root, RelOptInfo *input_rel, Assert(false); } } - - /* - * Now we may consider incremental sort on this path, but only - * when the path is not already sorted and when incremental sort - * is enabled. - */ - if (is_sorted || !enable_incremental_sort) - continue; - - /* Restore the input path (we might have added Sort on top). */ - path = path_original; - - /* no shared prefix, no point in building incremental sort */ - if (presorted_keys == 0) - continue; - - /* - * We should have already excluded pathkeys of length 1 because - * then presorted_keys > 0 would imply is_sorted was true. - */ - Assert(list_length(root->group_pathkeys) != 1); - - path = (Path *) create_incremental_sort_path(root, - grouped_rel, - path, - root->group_pathkeys, - presorted_keys, - -1.0); - - /* Now decide what to stick atop it */ - if (parse->groupingSets) - { - consider_groupingsets_paths(root, grouped_rel, - path, true, can_hash, - gd, agg_costs, dNumGroups); - } - else if (parse->hasAggs) - { - /* - * We have aggregation, possibly with plain GROUP BY. Make an - * AggPath. - */ - add_path(grouped_rel, (Path *) - create_agg_path(root, - grouped_rel, - path, - grouped_rel->reltarget, - parse->groupClause ? AGG_SORTED : AGG_PLAIN, - AGGSPLIT_SIMPLE, - parse->groupClause, - havingQual, - agg_costs, - dNumGroups)); - } - else if (parse->groupClause) - { - /* - * We have GROUP BY without aggregation or grouping sets. Make - * a GroupPath. - */ - add_path(grouped_rel, (Path *) - create_group_path(root, - grouped_rel, - path, - parse->groupClause, - havingQual, - dNumGroups)); - } - else - { - /* Other cases should have been handled above */ - Assert(false); - } } /* @@ -6376,100 +6414,130 @@ add_paths_to_grouping_rel(PlannerInfo *root, RelOptInfo *input_rel, { foreach(lc, partially_grouped_rel->pathlist) { + ListCell *lc2; Path *path = (Path *) lfirst(lc); Path *path_original = path; - bool is_sorted; - int presorted_keys; - is_sorted = pathkeys_count_contained_in(root->group_pathkeys, - path->pathkeys, - &presorted_keys); + List *pathkey_orderings = NIL; - /* - * Insert a Sort node, if required. But there's no point in - * sorting anything but the cheapest path. - */ - if (!is_sorted) + List *group_pathkeys = root->group_pathkeys; + List *group_clauses = parse->groupClause; + + /* generate alternative group orderings that might be useful */ + pathkey_orderings = get_useful_group_keys_orderings(root, + path->rows, + path->pathkeys, + group_pathkeys, + group_clauses); + + Assert(list_length(pathkey_orderings) > 0); + + /* process all potentially interesting grouping reorderings */ + foreach(lc2, pathkey_orderings) { - if (path != partially_grouped_rel->cheapest_total_path) - continue; - path = (Path *) create_sort_path(root, - grouped_rel, - path, - root->group_pathkeys, - -1.0); - } + bool is_sorted; + int presorted_keys = 0; + PathKeyInfo *info = (PathKeyInfo *) lfirst(lc2); - if (parse->hasAggs) - add_path(grouped_rel, (Path *) - create_agg_path(root, - grouped_rel, - path, - grouped_rel->reltarget, - parse->groupClause ? AGG_SORTED : AGG_PLAIN, - AGGSPLIT_FINAL_DESERIAL, - parse->groupClause, - havingQual, - agg_final_costs, - dNumGroups)); - else - add_path(grouped_rel, (Path *) - create_group_path(root, - grouped_rel, - path, - parse->groupClause, - havingQual, - dNumGroups)); + /* restore the path (we replace it in the loop) */ + path = path_original; - /* - * Now we may consider incremental sort on this path, but only - * when the path is not already sorted and when incremental - * sort is enabled. - */ - if (is_sorted || !enable_incremental_sort) - continue; + is_sorted = pathkeys_count_contained_in(info->pathkeys, + path->pathkeys, + &presorted_keys); - /* Restore the input path (we might have added Sort on top). */ - path = path_original; + /* + * Insert a Sort node, if required. But there's no point + * in sorting anything but the cheapest path. + */ + if (!is_sorted) + { + if (path != partially_grouped_rel->cheapest_total_path) + continue; + path = (Path *) create_sort_path(root, + grouped_rel, + path, + info->pathkeys, + -1.0); + } - /* no shared prefix, not point in building incremental sort */ - if (presorted_keys == 0) - continue; + if (parse->hasAggs) + add_path(grouped_rel, (Path *) + create_agg_path(root, + grouped_rel, + path, + grouped_rel->reltarget, + info->clauses ? AGG_SORTED : AGG_PLAIN, + AGGSPLIT_FINAL_DESERIAL, + info->clauses, + havingQual, + agg_final_costs, + dNumGroups)); + else + add_path(grouped_rel, (Path *) + create_group_path(root, + grouped_rel, + path, + info->clauses, + havingQual, + dNumGroups)); - /* - * We should have already excluded pathkeys of length 1 - * because then presorted_keys > 0 would imply is_sorted was - * true. - */ - Assert(list_length(root->group_pathkeys) != 1); + /* + * Now we may consider incremental sort on this path, but + * only when the path is not already sorted and when + * incremental sort is enabled. + */ + if (is_sorted || !enable_incremental_sort) + continue; - path = (Path *) create_incremental_sort_path(root, - grouped_rel, - path, - root->group_pathkeys, - presorted_keys, - -1.0); + /* + * Restore the input path (we might have added Sort on + * top). + */ + path = path_original; - if (parse->hasAggs) - add_path(grouped_rel, (Path *) - create_agg_path(root, - grouped_rel, - path, - grouped_rel->reltarget, - parse->groupClause ? AGG_SORTED : AGG_PLAIN, - AGGSPLIT_FINAL_DESERIAL, - parse->groupClause, - havingQual, - agg_final_costs, - dNumGroups)); - else - add_path(grouped_rel, (Path *) - create_group_path(root, - grouped_rel, - path, - parse->groupClause, - havingQual, - dNumGroups)); + /* + * no shared prefix, not point in building incremental + * sort + */ + if (presorted_keys == 0) + continue; + + /* + * We should have already excluded pathkeys of length 1 + * because then presorted_keys > 0 would imply is_sorted + * was true. + */ + Assert(list_length(root->group_pathkeys) != 1); + + path = (Path *) create_incremental_sort_path(root, + grouped_rel, + path, + info->pathkeys, + presorted_keys, + -1.0); + + if (parse->hasAggs) + add_path(grouped_rel, (Path *) + create_agg_path(root, + grouped_rel, + path, + grouped_rel->reltarget, + info->clauses ? AGG_SORTED : AGG_PLAIN, + AGGSPLIT_FINAL_DESERIAL, + info->clauses, + havingQual, + agg_final_costs, + dNumGroups)); + else + add_path(grouped_rel, (Path *) + create_group_path(root, + grouped_rel, + path, + info->clauses, + havingQual, + dNumGroups)); + } } } } @@ -6672,41 +6740,71 @@ create_partial_grouping_paths(PlannerInfo *root, */ foreach(lc, input_rel->pathlist) { + ListCell *lc2; Path *path = (Path *) lfirst(lc); - bool is_sorted; + Path *path_save = path; + + List *pathkey_orderings = NIL; - is_sorted = pathkeys_contained_in(root->group_pathkeys, - path->pathkeys); - if (path == cheapest_total_path || is_sorted) + List *group_pathkeys = root->group_pathkeys; + List *group_clauses = parse->groupClause; + + /* generate alternative group orderings that might be useful */ + pathkey_orderings = get_useful_group_keys_orderings(root, + path->rows, + path->pathkeys, + group_pathkeys, + group_clauses); + + Assert(list_length(pathkey_orderings) > 0); + + /* process all potentially interesting grouping reorderings */ + foreach(lc2, pathkey_orderings) { - /* Sort the cheapest partial path, if it isn't already */ - if (!is_sorted) - path = (Path *) create_sort_path(root, - partially_grouped_rel, - path, - root->group_pathkeys, - -1.0); + bool is_sorted; + int presorted_keys = 0; + PathKeyInfo *info = (PathKeyInfo *) lfirst(lc2); - if (parse->hasAggs) - add_path(partially_grouped_rel, (Path *) - create_agg_path(root, - partially_grouped_rel, - path, - partially_grouped_rel->reltarget, - parse->groupClause ? AGG_SORTED : AGG_PLAIN, - AGGSPLIT_INITIAL_SERIAL, - parse->groupClause, - NIL, - agg_partial_costs, - dNumPartialGroups)); - else - add_path(partially_grouped_rel, (Path *) - create_group_path(root, - partially_grouped_rel, - path, - parse->groupClause, - NIL, - dNumPartialGroups)); + /* restore the path (we replace it in the loop) */ + path = path_save; + + is_sorted = pathkeys_count_contained_in(info->pathkeys, + path->pathkeys, + &presorted_keys); + + if (path == cheapest_total_path || is_sorted) + { + /* Sort the cheapest partial path, if it isn't already */ + if (!is_sorted) + { + path = (Path *) create_sort_path(root, + partially_grouped_rel, + path, + info->pathkeys, + -1.0); + } + + if (parse->hasAggs) + add_path(partially_grouped_rel, (Path *) + create_agg_path(root, + partially_grouped_rel, + path, + partially_grouped_rel->reltarget, + info->clauses ? AGG_SORTED : AGG_PLAIN, + AGGSPLIT_INITIAL_SERIAL, + info->clauses, + NIL, + agg_partial_costs, + dNumPartialGroups)); + else + add_path(partially_grouped_rel, (Path *) + create_group_path(root, + partially_grouped_rel, + path, + info->clauses, + NIL, + dNumPartialGroups)); + } } } @@ -6716,6 +6814,8 @@ create_partial_grouping_paths(PlannerInfo *root, * We can also skip the entire loop when we only have a single-item * group_pathkeys because then we can't possibly have a presorted * prefix of the list without having the list be fully sorted. + * + * XXX Shouldn't this also consider the group-key-reordering? */ if (enable_incremental_sort && list_length(root->group_pathkeys) > 1) { @@ -6773,24 +6873,101 @@ create_partial_grouping_paths(PlannerInfo *root, /* Similar to above logic, but for partial paths. */ foreach(lc, input_rel->partial_pathlist) { + ListCell *lc2; Path *path = (Path *) lfirst(lc); Path *path_original = path; - bool is_sorted; - int presorted_keys; - is_sorted = pathkeys_count_contained_in(root->group_pathkeys, - path->pathkeys, - &presorted_keys); + List *pathkey_orderings = NIL; - if (path == cheapest_partial_path || is_sorted) + List *group_pathkeys = root->group_pathkeys; + List *group_clauses = parse->groupClause; + + /* generate alternative group orderings that might be useful */ + pathkey_orderings = get_useful_group_keys_orderings(root, + path->rows, + path->pathkeys, + group_pathkeys, + group_clauses); + + Assert(list_length(pathkey_orderings) > 0); + + /* process all potentially interesting grouping reorderings */ + foreach(lc2, pathkey_orderings) { - /* Sort the cheapest partial path, if it isn't already */ - if (!is_sorted) - path = (Path *) create_sort_path(root, - partially_grouped_rel, - path, - root->group_pathkeys, - -1.0); + bool is_sorted; + int presorted_keys = 0; + PathKeyInfo *info = (PathKeyInfo *) lfirst(lc2); + + /* restore the path (we replace it in the loop) */ + path = path_original; + + is_sorted = pathkeys_count_contained_in(info->pathkeys, + path->pathkeys, + &presorted_keys); + + if (path == cheapest_partial_path || is_sorted) + { + + /* Sort the cheapest partial path, if it isn't already */ + if (!is_sorted) + { + path = (Path *) create_sort_path(root, + partially_grouped_rel, + path, + info->pathkeys, + -1.0); + } + + if (parse->hasAggs) + add_partial_path(partially_grouped_rel, (Path *) + create_agg_path(root, + partially_grouped_rel, + path, + partially_grouped_rel->reltarget, + info->clauses ? AGG_SORTED : AGG_PLAIN, + AGGSPLIT_INITIAL_SERIAL, + info->clauses, + NIL, + agg_partial_costs, + dNumPartialPartialGroups)); + else + add_partial_path(partially_grouped_rel, (Path *) + create_group_path(root, + partially_grouped_rel, + path, + info->clauses, + NIL, + dNumPartialPartialGroups)); + } + + /* + * Now we may consider incremental sort on this path, but only + * when the path is not already sorted and when incremental + * sort is enabled. + */ + if (is_sorted || !enable_incremental_sort) + continue; + + /* Restore the input path (we might have added Sort on top). */ + path = path_original; + + /* no shared prefix, not point in building incremental sort */ + if (presorted_keys == 0) + continue; + + /* + * We should have already excluded pathkeys of length 1 + * because then presorted_keys > 0 would imply is_sorted was + * true. + */ + Assert(list_length(root->group_pathkeys) != 1); + + path = (Path *) create_incremental_sort_path(root, + partially_grouped_rel, + path, + info->pathkeys, + presorted_keys, + -1.0); if (parse->hasAggs) add_partial_path(partially_grouped_rel, (Path *) @@ -6798,9 +6975,9 @@ create_partial_grouping_paths(PlannerInfo *root, partially_grouped_rel, path, partially_grouped_rel->reltarget, - parse->groupClause ? AGG_SORTED : AGG_PLAIN, + info->clauses ? AGG_SORTED : AGG_PLAIN, AGGSPLIT_INITIAL_SERIAL, - parse->groupClause, + info->clauses, NIL, agg_partial_costs, dNumPartialPartialGroups)); @@ -6809,59 +6986,10 @@ create_partial_grouping_paths(PlannerInfo *root, create_group_path(root, partially_grouped_rel, path, - parse->groupClause, + info->clauses, NIL, dNumPartialPartialGroups)); } - - /* - * Now we may consider incremental sort on this path, but only - * when the path is not already sorted and when incremental sort - * is enabled. - */ - if (is_sorted || !enable_incremental_sort) - continue; - - /* Restore the input path (we might have added Sort on top). */ - path = path_original; - - /* no shared prefix, not point in building incremental sort */ - if (presorted_keys == 0) - continue; - - /* - * We should have already excluded pathkeys of length 1 because - * then presorted_keys > 0 would imply is_sorted was true. - */ - Assert(list_length(root->group_pathkeys) != 1); - - path = (Path *) create_incremental_sort_path(root, - partially_grouped_rel, - path, - root->group_pathkeys, - presorted_keys, - -1.0); - - if (parse->hasAggs) - add_partial_path(partially_grouped_rel, (Path *) - create_agg_path(root, - partially_grouped_rel, - path, - partially_grouped_rel->reltarget, - parse->groupClause ? AGG_SORTED : AGG_PLAIN, - AGGSPLIT_INITIAL_SERIAL, - parse->groupClause, - NIL, - agg_partial_costs, - dNumPartialPartialGroups)); - else - add_partial_path(partially_grouped_rel, (Path *) - create_group_path(root, - partially_grouped_rel, - path, - parse->groupClause, - NIL, - dNumPartialPartialGroups)); } } diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c index 9d912a84457..33a2cf72de2 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -2129,7 +2129,7 @@ fix_scan_expr_mutator(Node *node, fix_scan_expr_context *context) context); fix_expr_common(context->root, node); return expression_tree_mutator(node, fix_scan_expr_mutator, - (void *) context); + (void *) context, 0); } static bool @@ -2475,7 +2475,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); } /* @@ -2840,6 +2840,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) { @@ -2854,6 +2858,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, @@ -2928,7 +2935,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); } /* @@ -3057,7 +3064,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); } /* @@ -3152,7 +3159,7 @@ fix_windowagg_condition_expr_mutator(Node *node, return expression_tree_mutator(node, fix_windowagg_condition_expr_mutator, - (void *) context); + (void *) context, 0); } /* diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c index a1957883baf..16b0f2d37b4 100644 --- a/src/backend/optimizer/plan/subselect.c +++ b/src/backend/optimizer/plan/subselect.c @@ -712,7 +712,7 @@ convert_testexpr_mutator(Node *node, } return expression_tree_mutator(node, convert_testexpr_mutator, - (void *) context); + (void *) context, 0); } /* @@ -1898,7 +1898,7 @@ replace_correlation_vars_mutator(Node *node, PlannerInfo *root) } return expression_tree_mutator(node, replace_correlation_vars_mutator, - (void *) root); + (void *) root, 0); } /* @@ -2046,7 +2046,7 @@ process_sublinks_mutator(Node *node, process_sublinks_context *context) return expression_tree_mutator(node, process_sublinks_mutator, - (void *) &locContext); + (void *) &locContext, 0); } /* diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index f004fad1d92..4d882c045e8 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -621,7 +621,7 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root, * Append the child results together. */ path = (Path *) create_append_path(root, result_rel, pathlist, NIL, - NIL, NULL, 0, false, -1); + NIL, NULL, 0, false, -1, false); /* * For UNION ALL, we just need the Append path. For UNION, need to add @@ -678,7 +678,7 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root, create_append_path(root, result_rel, NIL, partial_pathlist, NIL, NULL, parallel_workers, enable_parallel_append, - -1); + -1, false); ppath = (Path *) create_gather_path(root, result_rel, ppath, result_rel->reltarget, NULL, NULL); @@ -788,7 +788,7 @@ generate_nonunion_paths(SetOperationStmt *op, PlannerInfo *root, * Append the child results together. */ path = (Path *) create_append_path(root, result_rel, pathlist, NIL, - NIL, NULL, 0, false, -1); + NIL, NULL, 0, false, -1, false); /* Identify the grouping semantics */ groupList = generate_setop_grouplist(op, tlist); diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c index 5c3d5a736ba..3664ed26e70 100644 --- a/src/backend/optimizer/util/appendinfo.c +++ b/src/backend/optimizer/util/appendinfo.c @@ -390,7 +390,7 @@ adjust_appendrel_attrs_mutator(Node *node, phv = (PlaceHolderVar *) expression_tree_mutator(node, adjust_appendrel_attrs_mutator, - (void *) context); + (void *) context, 0); /* now fix PlaceHolderVar's relid sets */ if (phv->phlevelsup == 0) phv->phrels = adjust_child_relids(phv->phrels, context->nappinfos, @@ -475,7 +475,7 @@ adjust_appendrel_attrs_mutator(Node *node, Assert(!IsA(node, JoinExpr)); return expression_tree_mutator(node, adjust_appendrel_attrs_mutator, - (void *) context); + (void *) context, 0); } /* diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index f2216f590e3..696ebacf7a6 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -2257,7 +2257,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. @@ -2389,7 +2389,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, @@ -2533,7 +2533,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 @@ -3898,7 +3898,7 @@ simplify_function(Oid funcid, Oid result_type, int32 result_typmod, args = expand_function_arguments(args, false, result_type, func_tuple); args = (List *) expression_tree_mutator((Node *) args, eval_const_expressions_mutator, - (void *) context); + (void *) context, 0); /* Argument processing done, give it back to the caller */ *args_p = args; } @@ -4753,7 +4753,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); } /* @@ -5221,7 +5221,7 @@ substitute_actual_srf_parameters_mutator(Node *node, } return expression_tree_mutator(node, substitute_actual_srf_parameters_mutator, - (void *) context); + (void *) context, 0); } /* diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c index 33affafb3d5..68bf2ebed36 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; @@ -1246,7 +1258,7 @@ create_append_path(PlannerInfo *root, List *subpaths, List *partial_subpaths, List *pathkeys, Relids required_outer, int parallel_workers, bool parallel_aware, - double rows) + double rows, bool pull_tlist) { AppendPath *pathnode = makeNode(AppendPath); ListCell *l; @@ -1256,6 +1268,7 @@ create_append_path(PlannerInfo *root, pathnode->path.pathtype = T_Append; pathnode->path.parent = rel; pathnode->path.pathtarget = rel->reltarget; + pathnode->pull_tlist = pull_tlist; /* * When generating an Append path for a partitioned table, there may be @@ -1342,7 +1355,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) @@ -1703,7 +1716,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; @@ -3829,9 +3843,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; @@ -3947,7 +3965,8 @@ reparameterize_path(PlannerInfo *root, Path *path, apath->path.pathkeys, required_outer, apath->path.parallel_workers, apath->path.parallel_aware, - -1); + -1, + apath->pull_tlist); } case T_Memoize: { diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 419f2ac55fa..0382fe95835 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -445,6 +445,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 3c75fd56f22..8ca7a21b0cb 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -40,14 +40,10 @@ typedef struct JoinHashEntry static void build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel, RelOptInfo *input_rel); -static List *build_joinrel_restrictlist(PlannerInfo *root, - RelOptInfo *joinrel, - RelOptInfo *outer_rel, - RelOptInfo *inner_rel); static void build_joinrel_joinlist(RelOptInfo *joinrel, RelOptInfo *outer_rel, RelOptInfo *inner_rel); -static List *subbuild_joinrel_restrictlist(RelOptInfo *joinrel, +static List *subbuild_joinrel_restrictlist(Relids joinrelids, List *joininfo_list, List *new_restrictlist); static List *subbuild_joinrel_joinlist(RelOptInfo *joinrel, @@ -600,7 +596,7 @@ build_join_rel(PlannerInfo *root, */ if (restrictlist_ptr) *restrictlist_ptr = build_joinrel_restrictlist(root, - joinrel, + joinrel->relids, outer_rel, inner_rel); return joinrel; @@ -707,7 +703,7 @@ build_join_rel(PlannerInfo *root, * caller might or might not need the restrictlist, but I need it anyway * for set_joinrel_size_estimates().) */ - restrictlist = build_joinrel_restrictlist(root, joinrel, + restrictlist = build_joinrel_restrictlist(root, joinrel->relids, outer_rel, inner_rel); if (restrictlist_ptr) *restrictlist_ptr = restrictlist; @@ -1059,7 +1055,7 @@ build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel, * the various joinlist entries ultimately refer to RestrictInfos * pushed into them by distribute_restrictinfo_to_rels(). * - * 'joinrel' is a join relation node + * 'joinrelids' is a join relation id set * 'outer_rel' and 'inner_rel' are a pair of relations that can be joined * to form joinrel. * @@ -1072,9 +1068,9 @@ build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel, * RestrictInfo nodes are no longer context-dependent. Instead, just include * the original nodes in the lists made for the join relation. */ -static List * +List * build_joinrel_restrictlist(PlannerInfo *root, - RelOptInfo *joinrel, + Relids joinrelids, RelOptInfo *outer_rel, RelOptInfo *inner_rel) { @@ -1085,8 +1081,8 @@ build_joinrel_restrictlist(PlannerInfo *root, * eliminating any duplicates (important since we will see many of the * same clauses arriving from both input relations). */ - result = subbuild_joinrel_restrictlist(joinrel, outer_rel->joininfo, NIL); - result = subbuild_joinrel_restrictlist(joinrel, inner_rel->joininfo, result); + result = subbuild_joinrel_restrictlist(joinrelids, outer_rel->joininfo, NIL); + result = subbuild_joinrel_restrictlist(joinrelids, inner_rel->joininfo, result); /* * Add on any clauses derived from EquivalenceClasses. These cannot be @@ -1095,7 +1091,7 @@ build_joinrel_restrictlist(PlannerInfo *root, */ result = list_concat(result, generate_join_implied_equalities(root, - joinrel->relids, + joinrelids, outer_rel->relids, inner_rel)); @@ -1120,18 +1116,66 @@ build_joinrel_joinlist(RelOptInfo *joinrel, joinrel->joininfo = result; } +typedef struct UniquePtrList { + List *unique_list; + HTAB *h; +} UniquePtrList; + +static void +addUniquePtrList(UniquePtrList *upl, void *v) +{ + if (upl->h != NULL || list_length(upl->unique_list) > 32) + { + bool found; + + if (upl->h == NULL) + { + HASHCTL hash_ctl; + ListCell *l; + + MemSet(&hash_ctl, 0, sizeof(hash_ctl)); + + hash_ctl.keysize = sizeof(void*); + hash_ctl.entrysize = sizeof(void*); + + upl->h = hash_create("UniquePtrList storage", 64, &hash_ctl, + HASH_BLOBS | HASH_ELEM); + + foreach(l, upl->unique_list) + { + void *k = lfirst(l); + + hash_search(upl->h, &k, HASH_ENTER, &found); + Assert(found == false); + } + } + + hash_search(upl->h, &v, HASH_ENTER, &found); + if (found == false) + upl->unique_list = lappend(upl->unique_list, v); + } + else + { + upl->unique_list = list_append_unique_ptr(upl->unique_list, v); + } +} + static List * -subbuild_joinrel_restrictlist(RelOptInfo *joinrel, +subbuild_joinrel_restrictlist(Relids joinrelids, List *joininfo_list, List *new_restrictlist) { ListCell *l; + UniquePtrList upl; + + memset(&upl, 0, sizeof(upl)); + upl.unique_list = new_restrictlist; foreach(l, joininfo_list) { RestrictInfo *rinfo = (RestrictInfo *) lfirst(l); - if (bms_is_subset(rinfo->required_relids, joinrel->relids)) + if (bms_is_subset(rinfo->required_relids, joinrelids)) { /* * This clause becomes a restriction clause for the joinrel, since @@ -1140,7 +1184,7 @@ subbuild_joinrel_restrictlist(RelOptInfo *joinrel, * different joinlists will have been multiply-linked rather than * copied, pointer equality should be a sufficient test.) */ - new_restrictlist = list_append_unique_ptr(new_restrictlist, rinfo); + addUniquePtrList(&upl, rinfo); } else { @@ -1151,7 +1195,8 @@ subbuild_joinrel_restrictlist(RelOptInfo *joinrel, } } - return new_restrictlist; + hash_destroy(upl.h); + return upl.unique_list; } static List * @@ -1160,6 +1205,10 @@ subbuild_joinrel_joinlist(RelOptInfo *joinrel, List *new_joininfo) { ListCell *l; + UniquePtrList upl; + + memset(&upl, 0, sizeof(upl)); + upl.unique_list = new_joininfo; /* Expected to be called only for join between parent relations. */ Assert(joinrel->reloptkind == RELOPT_JOINREL); @@ -1185,11 +1234,12 @@ subbuild_joinrel_joinlist(RelOptInfo *joinrel, * multiply-linked rather than copied, pointer equality should be * a sufficient test.) */ - new_joininfo = list_append_unique_ptr(new_joininfo, rinfo); + addUniquePtrList(&upl, rinfo); } } - return new_joininfo; + hash_destroy(upl.h); + return upl.unique_list; } diff --git a/src/backend/optimizer/util/var.c b/src/backend/optimizer/util/var.c index ebc6ce84b0b..3fcbcb68d3e 100644 --- a/src/backend/optimizer/util/var.c +++ b/src/backend/optimizer/util/var.c @@ -840,7 +840,7 @@ flatten_join_alias_vars_mutator(Node *node, phv = (PlaceHolderVar *) expression_tree_mutator(node, flatten_join_alias_vars_mutator, - (void *) context); + (void *) context, 0); /* now fix PlaceHolderVar's relid sets */ if (phv->phlevelsup == context->sublevels_up) { @@ -876,7 +876,7 @@ flatten_join_alias_vars_mutator(Node *node, Assert(!IsA(node, MinMaxAggInfo)); return expression_tree_mutator(node, flatten_join_alias_vars_mutator, - (void *) context); + (void *) context, 0); } /* diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 2ece5d71d1a..2ec029589f5 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -14456,7 +14456,7 @@ a_expr: c_expr { $$ = $1; } } | a_expr LIKE a_expr ESCAPE a_expr %prec LIKE { - FuncCall *n = makeFuncCall(SystemFuncName("like_escape"), + FuncCall *n = makeFuncCall(list_make1(makeString("like_escape")), list_make2($3, $5), COERCE_EXPLICIT_CALL, @2); @@ -14470,7 +14470,7 @@ a_expr: c_expr { $$ = $1; } } | a_expr NOT_LA LIKE a_expr ESCAPE a_expr %prec NOT_LA { - FuncCall *n = makeFuncCall(SystemFuncName("like_escape"), + FuncCall *n = makeFuncCall(list_make1(makeString("like_escape")), list_make2($4, $6), COERCE_EXPLICIT_CALL, @2); @@ -14484,7 +14484,7 @@ a_expr: c_expr { $$ = $1; } } | a_expr ILIKE a_expr ESCAPE a_expr %prec ILIKE { - FuncCall *n = makeFuncCall(SystemFuncName("like_escape"), + FuncCall *n = makeFuncCall(list_make1(makeString("like_escape")), list_make2($3, $5), COERCE_EXPLICIT_CALL, @2); @@ -14498,7 +14498,7 @@ a_expr: c_expr { $$ = $1; } } | a_expr NOT_LA ILIKE a_expr ESCAPE a_expr %prec NOT_LA { - FuncCall *n = makeFuncCall(SystemFuncName("like_escape"), + FuncCall *n = makeFuncCall(list_make1(makeString("like_escape")), list_make2($4, $6), COERCE_EXPLICIT_CALL, @2); @@ -14508,7 +14508,7 @@ a_expr: c_expr { $$ = $1; } | a_expr SIMILAR TO a_expr %prec SIMILAR { - FuncCall *n = makeFuncCall(SystemFuncName("similar_to_escape"), + FuncCall *n = makeFuncCall(list_make1(makeString("similar_to_escape")), list_make1($4), COERCE_EXPLICIT_CALL, @2); @@ -14517,7 +14517,7 @@ a_expr: c_expr { $$ = $1; } } | a_expr SIMILAR TO a_expr ESCAPE a_expr %prec SIMILAR { - FuncCall *n = makeFuncCall(SystemFuncName("similar_to_escape"), + FuncCall *n = makeFuncCall(list_make1(makeString("similar_to_escape")), list_make2($4, $6), COERCE_EXPLICIT_CALL, @2); @@ -14526,7 +14526,7 @@ a_expr: c_expr { $$ = $1; } } | a_expr NOT_LA SIMILAR TO a_expr %prec NOT_LA { - FuncCall *n = makeFuncCall(SystemFuncName("similar_to_escape"), + FuncCall *n = makeFuncCall(list_make1(makeString("similar_to_escape")), list_make1($5), COERCE_EXPLICIT_CALL, @2); @@ -14535,7 +14535,7 @@ a_expr: c_expr { $$ = $1; } } | a_expr NOT_LA SIMILAR TO a_expr ESCAPE a_expr %prec NOT_LA { - FuncCall *n = makeFuncCall(SystemFuncName("similar_to_escape"), + FuncCall *n = makeFuncCall(list_make1(makeString("similar_to_escape")), list_make2($5, $7), COERCE_EXPLICIT_CALL, @2); diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c index d821969a7d6..23173c82d89 100644 --- a/src/backend/parser/parse_clause.c +++ b/src/backend/parser/parse_clause.c @@ -1199,6 +1199,7 @@ transformFromClauseItem(ParseState *pstate, Node *n, &r_namespace); /* Remove the left-side RTEs from the namespace list again */ + pstate->p_namespace = list_truncate(pstate->p_namespace, sv_namespace_length); diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c index 863c25f303e..206a5444e22 100644 --- a/src/backend/parser/parse_relation.c +++ b/src/backend/parser/parse_relation.c @@ -2195,7 +2195,6 @@ addRangeTableEntryForJoin(ParseState *pstate, { RangeTblEntry *rte = makeNode(RangeTblEntry); Alias *eref; - int numaliases; ParseNamespaceItem *nsitem; Assert(pstate != NULL); @@ -2221,19 +2220,37 @@ addRangeTableEntryForJoin(ParseState *pstate, rte->join_using_alias = join_using_alias; rte->alias = alias; - eref = alias ? copyObject(alias) : makeAlias("unnamed_join", NIL); - numaliases = list_length(eref->colnames); - /* fill in any unspecified alias columns */ - if (numaliases < list_length(colnames)) - eref->colnames = list_concat(eref->colnames, - list_copy_tail(colnames, numaliases)); + if (alias) + { + int numaliases; - if (numaliases > list_length(colnames)) - ereport(ERROR, - (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), - errmsg("join expression \"%s\" has %d columns available but %d columns specified", - eref->aliasname, list_length(colnames), numaliases))); + eref = copyObject(alias); + + numaliases = list_length(eref->colnames); + + if (numaliases == 0) + { + eref->colnames = colnames; + } + else if (numaliases > 0 && numaliases < list_length(colnames)) + { + eref->colnames = list_concat(eref->colnames, + list_copy_tail(colnames, numaliases)); + list_free(colnames); + } + + if (numaliases > list_length(colnames)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), + errmsg("join expression \"%s\" has %d columns available but %d columns specified", + eref->aliasname, list_length(colnames), numaliases))); + } + else + { + eref = makeAlias("unnamed_join", NIL); + eref->colnames = colnames; + } rte->eref = eref; @@ -2880,8 +2897,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 8c8067ba721..35c06a42384 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 8d7ef91785e..e292c071099 100644 --- a/src/backend/storage/file/fd.c +++ b/src/backend/storage/file/fd.c @@ -948,6 +948,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; @@ -967,6 +968,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 (;;) { @@ -982,7 +992,7 @@ count_usable_fds(int max_to_probe, int *usable_fds, int *already_open) break; #endif - thisfd = dup(2); + thisfd = dup(fdTest); if (thisfd < 0) { /* Expect EMFILE or ENFILE, else it's fishy */ @@ -1009,6 +1019,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 8a5c5265247..dfc5d89e4f9 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 59310b708fb..93babee1383 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -18,6 +18,7 @@ #include <unistd.h> #include "access/transam.h" +#include "access/xlog.h" #include "miscadmin.h" #include "storage/backendid.h" #include "storage/ipc.h" @@ -127,8 +128,8 @@ * per iteration. */ -#define MAXNUMMESSAGES 4096 -#define MSGNUMWRAPAROUND (MAXNUMMESSAGES * 262144) +#define MAXNUMMESSAGES 16384 +#define MSGNUMWRAPAROUND (MAXNUMMESSAGES * 65536) #define CLEANUP_MIN (MAXNUMMESSAGES / 2) #define CLEANUP_QUANTUM (MAXNUMMESSAGES / 16) #define SIG_THRESHOLD (MAXNUMMESSAGES / 2) @@ -486,8 +487,16 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) max = segP->maxMsgNum; while (nthistime-- > 0) { - segP->buffer[max % MAXNUMMESSAGES] = *data++; - max++; + /* sync with SendSharedInvalidMessages() */ + if (!((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 3bb448627cd..ea34ee785cb 100644 --- a/src/backend/storage/lmgr/lwlock.c +++ b/src/backend/storage/lmgr/lwlock.c @@ -987,6 +987,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. @@ -1044,7 +1053,7 @@ LWLockWakeup(LWLock *lock) * another lock. */ pg_write_barrier(); - waiter->lwWaiting = false; + waiter->lwWaiting = LW_WS_NOT_WAITING; PGSemaphoreUnlock(waiter->sem); } } @@ -1065,7 +1074,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); @@ -1073,7 +1082,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 */ @@ -1100,8 +1109,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; @@ -1114,18 +1122,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) @@ -1137,8 +1140,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; @@ -1162,7 +1165,7 @@ LWLockDequeueSelf(LWLock *lock) for (;;) { PGSemaphoreLock(MyProc->sem); - if (!MyProc->lwWaiting) + if (MyProc->lwWaiting == LW_WS_NOT_WAITING) break; extraWaits++; } @@ -1313,7 +1316,7 @@ LWLockAcquire(LWLock *lock, LWLockMode mode) for (;;) { PGSemaphoreLock(proc->sem); - if (!proc->lwWaiting) + if (proc->lwWaiting == LW_WS_NOT_WAITING) break; extraWaits++; } @@ -1478,7 +1481,7 @@ LWLockAcquireOrWait(LWLock *lock, LWLockMode mode) for (;;) { PGSemaphoreLock(proc->sem); - if (!proc->lwWaiting) + if (proc->lwWaiting == LW_WS_NOT_WAITING) break; extraWaits++; } @@ -1694,7 +1697,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++; } @@ -1772,6 +1775,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. */ @@ -1787,7 +1794,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 1b514f44e33..c60707b9dbf 100644 --- a/src/backend/storage/lmgr/proc.c +++ b/src/backend/storage/lmgr/proc.c @@ -397,7 +397,7 @@ InitProcess(void) /* NB -- autovac launcher intentionally does not set IS_AUTOVACUUM */ if (IsAutoVacuumWorkerProcess()) MyProc->statusFlags |= PROC_IS_AUTOVACUUM; - MyProc->lwWaiting = false; + MyProc->lwWaiting = LW_WS_NOT_WAITING; MyProc->lwWaitMode = 0; MyProc->waitLock = NULL; MyProc->waitProcLock = NULL; @@ -579,7 +579,7 @@ InitAuxiliaryProcess(void) MyProc->isBackgroundWorker = IsBackgroundWorker; MyProc->delayChkptFlags = 0; MyProc->statusFlags = 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 631733bb660..4f0f8f4232f 100644 --- a/src/backend/storage/smgr/smgr.c +++ b/src/backend/storage/smgr/smgr.c @@ -459,7 +459,8 @@ smgrdounlinkall(SMgrRelation *rels, int nrels, bool isRedo) * closed our own smgr rel. */ for (i = 0; i < nrels; i++) - CacheInvalidateSmgr(rnodes[i]); + if (!SmgrIsTemp(rels[i])) + CacheInvalidateSmgr(rnodes[i]); /* * Delete the physical file(s). @@ -644,7 +645,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 66294ab4c8b..19a5881ce95 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -75,6 +75,7 @@ #include "tcop/pquery.h" #include "tcop/tcopprot.h" #include "tcop/utility.h" +#include "utils/builtins.h" #include "utils/lsyscache.h" #include "utils/memutils.h" #include "utils/ps_status.h" diff --git a/src/backend/utils/adt/like_support.c b/src/backend/utils/adt/like_support.c index 2d3aaaaf6bd..a4609a89861 100644 --- a/src/backend/utils/adt/like_support.c +++ b/src/backend/utils/adt/like_support.c @@ -55,20 +55,9 @@ #include "utils/selfuncs.h" #include "utils/varlena.h" - -typedef enum -{ - Pattern_Type_Like, - Pattern_Type_Like_IC, - Pattern_Type_Regex, - Pattern_Type_Regex_IC, - Pattern_Type_Prefix -} Pattern_Type; - -typedef enum -{ - Pattern_Prefix_None, Pattern_Prefix_Partial, Pattern_Prefix_Exact -} Pattern_Prefix_Status; +#include "catalog/pg_proc.h" +#include "utils/catcache.h" +#include "utils/syscache.h" static Node *like_regex_support(Node *rawreq, Pattern_Type ptype); static List *match_pattern_prefix(Node *leftop, @@ -108,6 +97,119 @@ static Datum string_to_datum(const char *str, Oid datatype); static Const *string_to_const(const char *str, Oid datatype); static Const *string_to_bytea_const(const char *str, size_t str_len); +/**************************************************************************** + * ---- ROUTINES FOR "SPECIAL" INDEXABLE OPERATORS FOR + * SPECIAL USER_DEFINED TYPES ---- + * -- teodor + ****************************************************************************/ + +static Oid mmPFPOid = InvalidOid; +static Oid mmGTOid = InvalidOid; +static Oid mcharOid = InvalidOid; +static Oid mvarcharOid = InvalidOid; + +#define HeapTupleGetOid(type, tuple) (((type)GETSTRUCT(tuple))->oid) + +static Oid +findTypeOid(char *typname) +{ + CatCList *catlist; + HeapTuple tup; + int n_members; + Oid typoid; + + catlist = SearchSysCacheList(TYPENAMENSP, 1, + CStringGetDatum(typname), 0, 0); + + n_members = catlist->n_members; + + if (n_members != 1) + { + ReleaseSysCacheList(catlist); + if (n_members > 1) + elog(ERROR,"There are %d candidates for '%s' type", + n_members, typname); + return InvalidOid; + } + + tup = &catlist->members[0]->tuple; + + typoid = HeapTupleGetOid(Form_pg_type, tup); + + ReleaseSysCacheList(catlist); + + return typoid; +} + +static bool +fillMCharOIDS() { + CatCList *catlist; + HeapTuple tup; + char *funcname = "mchar_pattern_fixed_prefix"; + int n_members; + + catlist = SearchSysCacheList(PROCNAMEARGSNSP, 1, + CStringGetDatum(funcname), 0, 0); + n_members = catlist->n_members; + + if (n_members != 1) { + ReleaseSysCacheList(catlist); + if (n_members > 1) + elog(ERROR,"There are %d candidates for '%s' function'", n_members, funcname); + return false; + } + + tup = &catlist->members[0]->tuple; + + if ( HeapTupleGetOid(Form_pg_proc, tup) != mmPFPOid ) { + char *quals_funcname = "mchar_greaterstring"; + Oid tmp_mmPFPOid = HeapTupleGetOid(Form_pg_proc, tup); + + ReleaseSysCacheList(catlist); + + mcharOid = findTypeOid("mchar"); + mvarcharOid = findTypeOid("mvarchar"); + + if ( mcharOid == InvalidOid || mvarcharOid == InvalidOid ) { + elog(LOG,"Can't find mchar/mvarvarchar types: mchar=%d mvarchar=%d", + mcharOid, mvarcharOid); + return false; + } + + catlist = SearchSysCacheList(PROCNAMEARGSNSP, 1, + CStringGetDatum(quals_funcname), 0, 0); + n_members = catlist->n_members; + + if ( n_members != 1 ) { + ReleaseSysCacheList(catlist); + if ( n_members > 1 ) + elog(ERROR,"There are %d candidates for '%s' function'", n_members, quals_funcname); + return false; + } + + tup = &catlist->members[0]->tuple; + mmGTOid = HeapTupleGetOid(Form_pg_proc, tup); + mmPFPOid = tmp_mmPFPOid; + } + + ReleaseSysCacheList(catlist); + + return true; +} + +static Pattern_Prefix_Status +mchar_pattern_fixed_prefix(Const *patt, Pattern_Type ptype, Const **prefix) +{ + if (!fillMCharOIDS()) + return Pattern_Prefix_None; + + return (Pattern_Prefix_Status)DatumGetInt32( OidFunctionCall3( + mmPFPOid, + PointerGetDatum(patt), + Int32GetDatum(ptype), + PointerGetDatum(prefix) + ) ); +} /* * Planner support functions for LIKE, regex, and related operators @@ -260,6 +362,7 @@ match_pattern_prefix(Node *leftop, Expr *expr; FmgrInfo ltproc; Const *greaterstr; + bool isMchar = false; /* * Can't do anything with a non-constant or NULL pattern argument. @@ -292,8 +395,16 @@ match_pattern_prefix(Node *leftop, /* * Try to extract a fixed prefix from the pattern. */ - pstatus = pattern_fixed_prefix(patt, ptype, expr_coll, - &prefix, NULL); + ldatatype = exprType(leftop); + if (fillMCharOIDS() && (ldatatype == mcharOid || + ldatatype == mvarcharOid)) + { + pstatus = mchar_pattern_fixed_prefix(patt, ptype, &prefix); + isMchar = true; + } + else + pstatus = pattern_fixed_prefix(patt, ptype, expr_coll, + &prefix, NULL); /* fail if no fixed prefix */ if (pstatus == Pattern_Prefix_None) @@ -308,7 +419,6 @@ match_pattern_prefix(Node *leftop, * selected operators also determine the needed type of the prefix * constant. */ - ldatatype = exprType(leftop); switch (ldatatype) { case TEXTOID: @@ -375,7 +485,16 @@ match_pattern_prefix(Node *leftop, break; default: /* Can't get here unless we're attached to the wrong operator */ - return NIL; + if (!isMchar) + return NIL; + collation_aware = false; + rdatatype = mvarcharOid; + ltopr = get_opfamily_member(opfamily, ldatatype, rdatatype, + BTLessStrategyNumber); + eqopr = get_opfamily_member(opfamily, ldatatype, rdatatype, + BTEqualStrategyNumber); + geopr = get_opfamily_member(opfamily, ldatatype, rdatatype, + BTGreaterEqualStrategyNumber); } /* @@ -387,9 +506,10 @@ match_pattern_prefix(Node *leftop, */ if (prefix->consttype != rdatatype) { - Assert(prefix->consttype == TEXTOID && - rdatatype == BPCHAROID); - prefix->consttype = rdatatype; + Assert(isMchar || (prefix->consttype == TEXTOID && + rdatatype == BPCHAROID)); + if (!isMchar) + prefix->consttype = rdatatype; } /* @@ -458,7 +578,12 @@ match_pattern_prefix(Node *leftop, if (!op_in_opfamily(ltopr, opfamily)) return result; fmgr_info(get_opcode(ltopr), <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 db843a0fbf0..a115692102a 100644 --- a/src/backend/utils/adt/rowtypes.c +++ b/src/backend/utils/adt/rowtypes.c @@ -28,7 +28,6 @@ #include "utils/lsyscache.h" #include "utils/typcache.h" - /* * structure to cache metadata needed for record I/O */ @@ -807,6 +806,9 @@ record_cmp(FunctionCallInfo fcinfo) { HeapTupleHeader record1 = PG_GETARG_HEAPTUPLEHEADER(0); HeapTupleHeader record2 = PG_GETARG_HEAPTUPLEHEADER(1); + int record_cmp_prefix = + (PG_NARGS() == 3 && PG_GETARG_INT32(2) > 0) ? + PG_GETARG_INT32(2) : INT_MAX; int result = 0; Oid tupType1; Oid tupType2; @@ -891,6 +893,9 @@ record_cmp(FunctionCallInfo fcinfo) nulls2 = (bool *) palloc(ncolumns2 * sizeof(bool)); heap_deform_tuple(&tuple2, tupdesc2, values2, nulls2); + ncolumns1 = Min(ncolumns1, record_cmp_prefix); + ncolumns2 = Min(ncolumns2, record_cmp_prefix); + /* * Scan corresponding columns, allowing for dropped columns in different * places in the two rows. i1 and i2 are physical column indexes, j is @@ -1051,6 +1056,9 @@ record_eq(PG_FUNCTION_ARGS) { HeapTupleHeader record1 = PG_GETARG_HEAPTUPLEHEADER(0); HeapTupleHeader record2 = PG_GETARG_HEAPTUPLEHEADER(1); + int record_cmp_prefix = + (PG_NARGS() == 3 && PG_GETARG_INT32(2) > 0) ? + PG_GETARG_INT32(2) : INT_MAX; bool result = true; Oid tupType1; Oid tupType2; @@ -1135,6 +1143,9 @@ record_eq(PG_FUNCTION_ARGS) nulls2 = (bool *) palloc(ncolumns2 * sizeof(bool)); heap_deform_tuple(&tuple2, tupdesc2, values2, nulls2); + ncolumns1 = Min(ncolumns1, record_cmp_prefix); + ncolumns2 = Min(ncolumns2, record_cmp_prefix); + /* * Scan corresponding columns, allowing for dropped columns in different * places in the two rows. i1 and i2 are physical column indexes, j is diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index a1c1831cf13..c4eb4b85cb6 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -40,6 +40,7 @@ #include "catalog/pg_type.h" #include "commands/defrem.h" #include "commands/tablespace.h" +#include "common/hashfn.h" #include "common/keywords.h" #include "executor/spi.h" #include "funcapi.h" @@ -291,6 +292,8 @@ typedef struct int *leftattnos; /* left-child varattnos of join cols, or 0 */ int *rightattnos; /* right-child varattnos of join cols, or 0 */ List *usingNames; /* names assigned to merged columns */ + + HTAB *all_names; /* hash to store all names colname_is_unique() */ } deparse_columns; /* This macro is analogous to rt_fetch(), but for deparse_columns structs */ @@ -372,6 +375,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, @@ -4702,7 +4706,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]; @@ -4751,7 +4758,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]; @@ -4776,6 +4786,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? * @@ -4788,6 +4821,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++) { @@ -4839,6 +4941,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 2dd399d5e37..0d1c9777931 100644 --- a/src/backend/utils/adt/selfuncs.c +++ b/src/backend/utils/adt/selfuncs.c @@ -145,13 +145,14 @@ get_relation_stats_hook_type get_relation_stats_hook = NULL; get_index_stats_hook_type get_index_stats_hook = NULL; static double eqsel_internal(PG_FUNCTION_ARGS, bool negate); -static double eqjoinsel_inner(Oid opfuncoid, Oid collation, +static double eqjoinsel_inner(Oid operator, Oid opfuncoid, Oid collation, VariableStatData *vardata1, VariableStatData *vardata2, double nd1, double nd2, bool isdefault1, bool isdefault2, AttStatsSlot *sslot1, AttStatsSlot *sslot2, Form_pg_statistic stats1, Form_pg_statistic stats2, - bool have_mcvs1, bool have_mcvs2); + bool have_mcvs1, bool have_mcvs2, + int record_cmp_prefix); static double eqjoinsel_semi(Oid opfuncoid, Oid collation, VariableStatData *vardata1, VariableStatData *vardata2, double nd1, double nd2, @@ -159,7 +160,8 @@ static double eqjoinsel_semi(Oid opfuncoid, Oid collation, AttStatsSlot *sslot1, AttStatsSlot *sslot2, Form_pg_statistic stats1, Form_pg_statistic stats2, bool have_mcvs1, bool have_mcvs2, - RelOptInfo *inner_rel); + RelOptInfo *inner_rel, + int record_cmp_prefix); static bool estimate_multivariate_ndistinct(PlannerInfo *root, RelOptInfo *rel, List **varinfos, double *ndistinct); static bool convert_to_scalar(Datum value, Oid valuetypid, Oid collid, @@ -211,6 +213,20 @@ static bool get_actual_variable_endpoint(Relation heapRel, Datum *endpointDatum); static RelOptInfo *find_join_input_rel(PlannerInfo *root, Relids relids); +static bool +join_is_reversed_variables(SpecialJoinInfo *sjinfo, + VariableStatData *vardata1, VariableStatData *vardata2) +{ + if (vardata1->rel && + bms_is_subset(vardata1->rel->relids, sjinfo->syn_righthand)) + return true; /* var1 is on RHS */ + else if (vardata2->rel && + bms_is_subset(vardata2->rel->relids, sjinfo->syn_lefthand)) + return true; /* var2 is on LHS */ + else + return false; +} + /* * eqsel - Selectivity of "=" for any data types. @@ -283,6 +299,34 @@ eqsel_internal(PG_FUNCTION_ARGS, bool negate) return selec; } +static bool +get_cached_attstatsslot(AttStatsSlot *sslot, VariableStatData *vardata, + int reqkind, Oid reqop, int flags) +{ + if (vardata->sslots) + { + /* + * vardata has somewhere cache + */ + AttStatsSlot *sslotp; + + sslotp = fill_attstatsslot(vardata->sslots, + vardata->statsTuple, + reqkind, reqop, flags); + + if (sslotp) + { + *sslot = *sslotp; + return true; + } + } + + return get_attstatsslot(sslot, vardata->statsTuple, + reqkind, reqop, + flags); +} + + /* * var_eq_const --- eqsel for var = const case * @@ -292,6 +336,15 @@ double var_eq_const(VariableStatData *vardata, Oid operator, Oid collation, Datum constval, bool constisnull, bool varonleft, bool negate) +{ + return eqconst_selectivity(operator, collation, vardata, constval, + constisnull, varonleft, negate, -1); +} + +Selectivity +eqconst_selectivity(Oid operator, Oid collation, + VariableStatData *vardata, Datum constval, bool constisnull, + bool varonleft, bool negate, int record_cmp_prefix) { double selec; double nullfrac = 0.0; @@ -324,7 +377,8 @@ var_eq_const(VariableStatData *vardata, Oid operator, Oid collation, * different from ours, but it's much more likely to be right than * ignoring the information.) */ - if (vardata->isunique && vardata->rel && vardata->rel->tuples >= 1.0) + if (vardata->isunique && vardata->rel && vardata->rel->tuples >= 1.0 && + record_cmp_prefix <= 0) { selec = 1.0 / vardata->rel->tuples; } @@ -343,11 +397,11 @@ var_eq_const(VariableStatData *vardata, Oid operator, Oid collation, * don't like this, maybe you shouldn't be using eqsel for your * operator...) */ - if (get_attstatsslot(&sslot, vardata->statsTuple, - STATISTIC_KIND_MCV, InvalidOid, - ATTSTATSSLOT_VALUES | ATTSTATSSLOT_NUMBERS)) + if (get_cached_attstatsslot(&sslot, vardata, + STATISTIC_KIND_MCV, InvalidOid, + ATTSTATSSLOT_VALUES | ATTSTATSSLOT_NUMBERS)) { - LOCAL_FCINFO(fcinfo, 2); + LOCAL_FCINFO(fcinfo, 3); FmgrInfo eqproc; fmgr_info(opfuncoid, &eqproc); @@ -358,15 +412,17 @@ var_eq_const(VariableStatData *vardata, Oid operator, Oid collation, * eqproc returns NULL, though really equality functions should * never do that. */ - InitFunctionCallInfoData(*fcinfo, &eqproc, 2, collation, + InitFunctionCallInfoData(*fcinfo, &eqproc, 3, collation, NULL, NULL); fcinfo->args[0].isnull = false; fcinfo->args[1].isnull = false; + fcinfo->args[2].isnull = false; /* be careful to apply operator right way 'round */ if (varonleft) fcinfo->args[1].value = constval; else fcinfo->args[0].value = constval; + fcinfo->args[2].value = Int32GetDatum(record_cmp_prefix); for (i = 0; i < sslot.nvalues; i++) { @@ -1016,6 +1072,138 @@ generic_restriction_selectivity(PlannerInfo *root, Oid oproid, Oid collation, return selec; } +/* + * Binary search of bound constval in histogramm + */ +static int +prefix_record_histogram_search(AttStatsSlot *sslot, int start, + Datum constval, int record_cmp_prefix, + FmgrInfo *opproc, bool isgt) +{ + int lobound = start; /* first possible slot to search */ + int hibound = sslot->nvalues; /* last+1 slot to search */ + + while (lobound < hibound) + { + int probe = (lobound + hibound) / 2; + bool ltcmp; + + ltcmp = DatumGetBool(FunctionCall3Coll(opproc, + DEFAULT_COLLATION_OID, + sslot->values[probe], + constval, + Int32GetDatum(record_cmp_prefix))); + if (isgt) + ltcmp = !ltcmp; + if (ltcmp) + lobound = probe + 1; + else + hibound = probe; + } + + return lobound; +} + +/* + * Simple function to estimate selctivity by prefix of record, it just counts + * number of histogram bins matched by record prefix - similar to + * histogram_selectivity() but it knows about sortability of record + */ +double +prefix_record_histogram_selectivity(VariableStatData *vardata, + Datum constvalLeft, Datum constvalRight, + int record_cmp_prefix, + double ndistinct,int *n_bins) +{ + double result = -1.0; + AttStatsSlot sslot; + + if (HeapTupleIsValid(vardata->statsTuple) && + get_cached_attstatsslot(&sslot, vardata, + STATISTIC_KIND_HISTOGRAM, InvalidOid, + ATTSTATSSLOT_VALUES)) + { + FmgrInfo opprocLT, opprocGT; + int start = -1, + end = -1; + + + if (sslot.nvalues > 2) + { + fmgr_info(F_RECORD_GE, &opprocGT); + fmgr_info(F_RECORD_LE, &opprocLT); + + start = prefix_record_histogram_search(&sslot, 0, constvalLeft, + record_cmp_prefix, + &opprocGT, true); + if (start < 0) + start = 0; + end = prefix_record_histogram_search(&sslot, start, constvalRight, + -1, + &opprocLT, false); + if (end >= sslot.nvalues) + end = sslot.nvalues - 1; + } + else + { + fmgr_info(F_RECORD_GT, &opprocGT); + fmgr_info(F_RECORD_LE, &opprocLT); + + /* + * Find first bin which start border is less than constant + */ + for (start = sslot.nvalues - 1; start >= 0; start--) + { + if (DatumGetBool(FunctionCall3Coll(&opprocGT, + DEFAULT_COLLATION_OID, + constvalLeft, + sslot.values[start], + Int32GetDatum(record_cmp_prefix)))) + break; + } + + if (start < 0) + start=0; + + /* + * Find last bin which end border is less than constant + */ + for (end = start; end <= sslot.nvalues - 2; end ++) + { + if (DatumGetBool(FunctionCall3Coll(&opprocLT, + DEFAULT_COLLATION_OID, + constvalRight, + sslot.values[end + 1], + Int32GetDatum(-1)))) + break; + } + } + + if (opprocGT.fn_extra) + pfree(opprocGT.fn_extra); + if (opprocLT.fn_extra) + pfree(opprocLT.fn_extra); + + *n_bins = (start >= end) ? 0 : end - start; + result = (start >= end) ? 0.5 : end - start; + result /= ((double) (sslot.nvalues)); + + free_attstatsslot(&sslot); + + if (*n_bins == 0 && ndistinct > 1) + { + double ntuples = vardata->rel->tuples; + double ntuplesbin = vardata->rel->tuples / sslot.nvalues; + + result *= (1 - pow((ntuples - ntuplesbin) / ntuples, + ntuples / ndistinct)); + } + } + + return result; + +} + /* * ineq_histogram_selectivity - Examine the histogram for scalarineqsel * @@ -2244,11 +2432,32 @@ eqjoinsel(PG_FUNCTION_ARGS) JoinType jointype = (JoinType) PG_GETARG_INT16(3); #endif SpecialJoinInfo *sjinfo = (SpecialJoinInfo *) PG_GETARG_POINTER(4); - Oid collation = PG_GET_COLLATION(); + VariableStatData vardata1; + VariableStatData vardata2; + Selectivity s; + Oid collation = PG_GET_COLLATION(); + + get_join_variables(root, args, sjinfo, + &vardata1, &vardata2, NULL); + + + s = eqjoin_selectivity(root, operator, collation, &vardata1, &vardata2, + sjinfo, -1); + + ReleaseVariableStats(vardata1); + ReleaseVariableStats(vardata2); + + PG_RETURN_FLOAT8((float8)s); +} + +Selectivity +eqjoin_selectivity(PlannerInfo *root, Oid operator, Oid collation, + VariableStatData* vardata1, + VariableStatData* vardata2, SpecialJoinInfo *sjinfo, + int record_cmp_prefix) +{ double selec; double selec_inner; - VariableStatData vardata1; - VariableStatData vardata2; double nd1; double nd2; bool isdefault1; @@ -2263,45 +2472,45 @@ eqjoinsel(PG_FUNCTION_ARGS) bool join_is_reversed; RelOptInfo *inner_rel; - get_join_variables(root, args, sjinfo, - &vardata1, &vardata2, &join_is_reversed); + join_is_reversed = join_is_reversed_variables(sjinfo, vardata1, vardata2); - nd1 = get_variable_numdistinct(&vardata1, &isdefault1); - nd2 = get_variable_numdistinct(&vardata2, &isdefault2); + nd1 = get_variable_numdistinct(vardata1, &isdefault1); + nd2 = get_variable_numdistinct(vardata2, &isdefault2); opfuncoid = get_opcode(operator); memset(&sslot1, 0, sizeof(sslot1)); memset(&sslot2, 0, sizeof(sslot2)); - if (HeapTupleIsValid(vardata1.statsTuple)) + if (HeapTupleIsValid(vardata1->statsTuple)) { /* note we allow use of nullfrac regardless of security check */ - stats1 = (Form_pg_statistic) GETSTRUCT(vardata1.statsTuple); - if (statistic_proc_security_check(&vardata1, opfuncoid)) - have_mcvs1 = get_attstatsslot(&sslot1, vardata1.statsTuple, - STATISTIC_KIND_MCV, InvalidOid, - ATTSTATSSLOT_VALUES | ATTSTATSSLOT_NUMBERS); + stats1 = (Form_pg_statistic) GETSTRUCT(vardata1->statsTuple); + if (statistic_proc_security_check(vardata1, opfuncoid)) + have_mcvs1 = get_cached_attstatsslot(&sslot1, vardata1, + STATISTIC_KIND_MCV, InvalidOid, + ATTSTATSSLOT_VALUES | ATTSTATSSLOT_NUMBERS); } - if (HeapTupleIsValid(vardata2.statsTuple)) + if (HeapTupleIsValid(vardata2->statsTuple)) { /* note we allow use of nullfrac regardless of security check */ - stats2 = (Form_pg_statistic) GETSTRUCT(vardata2.statsTuple); - if (statistic_proc_security_check(&vardata2, opfuncoid)) - have_mcvs2 = get_attstatsslot(&sslot2, vardata2.statsTuple, - STATISTIC_KIND_MCV, InvalidOid, - ATTSTATSSLOT_VALUES | ATTSTATSSLOT_NUMBERS); + stats2 = (Form_pg_statistic) GETSTRUCT(vardata2->statsTuple); + if (statistic_proc_security_check(vardata2, opfuncoid)) + have_mcvs2 = get_cached_attstatsslot(&sslot2, vardata2, + STATISTIC_KIND_MCV, InvalidOid, + ATTSTATSSLOT_VALUES | ATTSTATSSLOT_NUMBERS); } /* We need to compute the inner-join selectivity in all cases */ - selec_inner = eqjoinsel_inner(opfuncoid, collation, - &vardata1, &vardata2, + selec_inner = eqjoinsel_inner(operator, opfuncoid, collation, + vardata1, vardata2, nd1, nd2, isdefault1, isdefault2, &sslot1, &sslot2, stats1, stats2, - have_mcvs1, have_mcvs2); + have_mcvs1, have_mcvs2, + record_cmp_prefix); switch (sjinfo->jointype) { @@ -2323,26 +2532,28 @@ eqjoinsel(PG_FUNCTION_ARGS) if (!join_is_reversed) selec = eqjoinsel_semi(opfuncoid, collation, - &vardata1, &vardata2, + vardata1, vardata2, nd1, nd2, isdefault1, isdefault2, &sslot1, &sslot2, stats1, stats2, have_mcvs1, have_mcvs2, - inner_rel); + inner_rel, + record_cmp_prefix); else { Oid commop = get_commutator(operator); Oid commopfuncoid = OidIsValid(commop) ? get_opcode(commop) : InvalidOid; selec = eqjoinsel_semi(commopfuncoid, collation, - &vardata2, &vardata1, + vardata2, vardata1, nd2, nd1, isdefault2, isdefault1, &sslot2, &sslot1, stats2, stats1, have_mcvs2, have_mcvs1, - inner_rel); + inner_rel, + record_cmp_prefix); } /* @@ -2368,12 +2579,132 @@ eqjoinsel(PG_FUNCTION_ARGS) free_attstatsslot(&sslot1); free_attstatsslot(&sslot2); - ReleaseVariableStats(vardata1); - ReleaseVariableStats(vardata2); - CLAMP_PROBABILITY(selec); - PG_RETURN_FLOAT8((float8) selec); + return selec; +} + +static int +cmp_vardata(FmgrInfo *eqproc, FmgrInfo *ltproc, + Datum v1, Datum v2, int record_cmp_prefix) +{ + int cmp; + + cmp = DatumGetBool(FunctionCall3Coll(ltproc, + DEFAULT_COLLATION_OID, + v1, v2, + Int32GetDatum(record_cmp_prefix))); + + if (cmp) + return -1; + + cmp = DatumGetBool(FunctionCall3Coll(eqproc, + DEFAULT_COLLATION_OID, + v1, v2, + Int32GetDatum(record_cmp_prefix))); + + return !cmp; +} +static double +eqjoinsel_histogram(Oid eqop, + VariableStatData *vardata1, VariableStatData *vardata2, + int record_cmp_prefix, double nd1, double nd2) +{ + bool have_hist1 = false; + bool have_hist2 = false; + AttStatsSlot sslot1; + AttStatsSlot sslot2; + int i1 = 0, i2 = 0; + double n1 = 0.0, n2 = 0.0; + double result = -1.0; + FmgrInfo eqproc, ltproc; + Oid orderop = InvalidOid; + List *opfamilies; + ListCell *lc; + + if (!(HeapTupleIsValid(vardata1->statsTuple) && + HeapTupleIsValid(vardata2->statsTuple))) + return result; + + memset(&sslot1, 0, sizeof(sslot1)); + memset(&sslot2, 0, sizeof(sslot2)); + + have_hist1 = get_cached_attstatsslot(&sslot1, vardata1, + STATISTIC_KIND_HISTOGRAM, InvalidOid, + ATTSTATSSLOT_VALUES); + have_hist2 = get_cached_attstatsslot(&sslot2, vardata2, + STATISTIC_KIND_HISTOGRAM, InvalidOid, + ATTSTATSSLOT_VALUES); + + if (!(have_hist1 && have_hist2)) + goto out; + + opfamilies = get_mergejoin_opfamilies(eqop); + foreach(lc, opfamilies) { + Oid opf = lfirst_oid(lc); + + orderop = get_opfamily_member(opf, vardata1->vartype, vardata2->vartype, + BTLessStrategyNumber); + + if (OidIsValid(orderop)) + break; + } + + /* == from fulleq, for example */ + if (!OidIsValid(orderop)) + goto out; + + fmgr_info(get_opcode(eqop), &eqproc); + fmgr_info(get_opcode(orderop), <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; } /* @@ -2383,13 +2714,14 @@ eqjoinsel(PG_FUNCTION_ARGS) * that it's worth trying to distinguish them here. */ static double -eqjoinsel_inner(Oid opfuncoid, Oid collation, +eqjoinsel_inner(Oid operator, Oid opfuncoid, Oid collation, VariableStatData *vardata1, VariableStatData *vardata2, double nd1, double nd2, bool isdefault1, bool isdefault2, AttStatsSlot *sslot1, AttStatsSlot *sslot2, Form_pg_statistic stats1, Form_pg_statistic stats2, - bool have_mcvs1, bool have_mcvs2) + bool have_mcvs1, bool have_mcvs2, + int record_cmp_prefix) { double selec; @@ -2407,7 +2739,7 @@ eqjoinsel_inner(Oid opfuncoid, Oid collation, * results", Technical Report 1018, Computer Science Dept., University * of Wisconsin, Madison, March 1991 (available from ftp.cs.wisc.edu). */ - LOCAL_FCINFO(fcinfo, 2); + LOCAL_FCINFO(fcinfo, 3); FmgrInfo eqproc; bool *hasmatch1; bool *hasmatch2; @@ -2433,10 +2765,12 @@ eqjoinsel_inner(Oid opfuncoid, Oid collation, * returns NULL, though really equality functions should never do * that. */ - InitFunctionCallInfoData(*fcinfo, &eqproc, 2, collation, + InitFunctionCallInfoData(*fcinfo, &eqproc, 3, collation, NULL, NULL); fcinfo->args[0].isnull = false; fcinfo->args[1].isnull = false; + fcinfo->args[2].isnull = false; + fcinfo->args[2].value = Int32GetDatum(record_cmp_prefix); hasmatch1 = (bool *) palloc0(sslot1->nvalues * sizeof(bool)); hasmatch2 = (bool *) palloc0(sslot2->nvalues * sizeof(bool)); @@ -2562,11 +2896,34 @@ eqjoinsel_inner(Oid opfuncoid, Oid collation, double nullfrac1 = stats1 ? stats1->stanullfrac : 0.0; double nullfrac2 = stats2 ? stats2->stanullfrac : 0.0; - selec = (1.0 - nullfrac1) * (1.0 - nullfrac2); - if (nd1 > nd2) - selec /= nd1; - else - selec /= nd2; + if (isdefault1 && vardata1->rel && nd1 > vardata1->rel->rows) + { + nd1 = vardata1->rel->rows; + if (nd1 == 0.0) + nd1 = 1.0; + } + + if (isdefault2 && vardata2->rel && nd2 > vardata2->rel->rows) + { + nd2 = vardata2->rel->rows; + if (nd2 == 0.0) + nd2 = 1.0; + } + + selec = eqjoinsel_histogram(operator, vardata1, vardata2, + record_cmp_prefix, nd1, nd2); + + if (selec < 0) + { + selec = 1.0; + + if (nd1 > nd2) + selec /= nd1; + else + selec /= nd2; + } + + selec *= (1.0 - nullfrac1) * (1.0 - nullfrac2); } return selec; @@ -2587,7 +2944,8 @@ eqjoinsel_semi(Oid opfuncoid, Oid collation, AttStatsSlot *sslot1, AttStatsSlot *sslot2, Form_pg_statistic stats1, Form_pg_statistic stats2, bool have_mcvs1, bool have_mcvs2, - RelOptInfo *inner_rel) + RelOptInfo *inner_rel, + int record_cmp_prefix) { double selec; @@ -2634,7 +2992,7 @@ eqjoinsel_semi(Oid opfuncoid, Oid collation, * lists. We still have to estimate for the remaining population, but * in a skewed distribution this gives us a big leg up in accuracy. */ - LOCAL_FCINFO(fcinfo, 2); + LOCAL_FCINFO(fcinfo, 3); FmgrInfo eqproc; bool *hasmatch1; bool *hasmatch2; @@ -2663,10 +3021,12 @@ eqjoinsel_semi(Oid opfuncoid, Oid collation, * returns NULL, though really equality functions should never do * that. */ - InitFunctionCallInfoData(*fcinfo, &eqproc, 2, collation, + InitFunctionCallInfoData(*fcinfo, &eqproc, 3, collation, NULL, NULL); fcinfo->args[0].isnull = false; fcinfo->args[1].isnull = false; + fcinfo->args[2].isnull = false; + fcinfo->args[2].value = Int32GetDatum(record_cmp_prefix); hasmatch1 = (bool *) palloc0(sslot1->nvalues * sizeof(bool)); hasmatch2 = (bool *) palloc0(clamped_nvalues2 * sizeof(bool)); @@ -3368,11 +3728,29 @@ double estimate_num_groups(PlannerInfo *root, List *groupExprs, double input_rows, List **pgset, EstimationInfo *estinfo) { - List *varinfos = NIL; + return estimate_num_groups_incremental(root, groupExprs, + input_rows, pgset, estinfo, + NULL, 0); +} + +/* + * estimate_num_groups_incremental + * An estimate_num_groups variant, optimized for cases that are adding the + * expressions incrementally (e.g. one by one). + */ +double +estimate_num_groups_incremental(PlannerInfo *root, List *groupExprs, + double input_rows, + List **pgset, EstimationInfo *estinfo, + List **cache_varinfos, int prevNExprs) +{ + List *varinfos = (cache_varinfos) ? *cache_varinfos : NIL; double srf_multiplier = 1.0; double numdistinct; ListCell *l; - int i; + int i, + j, + k; /* Zero the estinfo output parameter, if non-NULL */ if (estinfo != NULL) @@ -3403,7 +3781,7 @@ estimate_num_groups(PlannerInfo *root, List *groupExprs, double input_rows, */ numdistinct = 1.0; - i = 0; + i = j = 0; foreach(l, groupExprs) { Node *groupexpr = (Node *) lfirst(l); @@ -3412,6 +3790,14 @@ estimate_num_groups(PlannerInfo *root, List *groupExprs, double input_rows, List *varshere; ListCell *l2; + /* was done on previous call */ + if (cache_varinfos && j++ < prevNExprs) + { + if (pgset) + i++; /* to keep in sync with lines below */ + continue; + } + /* is expression in this grouping set? */ if (pgset && !list_member_int(*pgset, i++)) continue; @@ -3461,6 +3847,9 @@ estimate_num_groups(PlannerInfo *root, List *groupExprs, double input_rows, } ReleaseVariableStats(vardata); + if (list_length(varinfos) > 2*list_length(groupExprs)) + continue; + /* * Else pull out the component Vars. Handle PlaceHolderVars by * recursing into their arguments (effectively assuming that the @@ -3481,13 +3870,21 @@ estimate_num_groups(PlannerInfo *root, List *groupExprs, double input_rows, if (varshere == NIL) { if (contain_volatile_functions(groupexpr)) + { + if (cache_varinfos) + *cache_varinfos = varinfos; return input_rows; + } continue; } + if (list_length(varshere) >= 8) + continue; + /* * Else add variables to varinfos list */ + k = 0; foreach(l2, varshere) { Node *var = (Node *) lfirst(l2); @@ -3495,9 +3892,15 @@ estimate_num_groups(PlannerInfo *root, List *groupExprs, double input_rows, examine_variable(root, var, 0, &vardata); varinfos = add_unique_group_var(root, varinfos, var, &vardata); ReleaseVariableStats(vardata); + + if (++k > 4) + break; } } + if (cache_varinfos) + *cache_varinfos = varinfos; + /* * If now no Vars, we must have an all-constant or all-boolean GROUP BY * list. @@ -4904,14 +5307,8 @@ get_join_variables(PlannerInfo *root, List *args, SpecialJoinInfo *sjinfo, examine_variable(root, left, 0, vardata1); examine_variable(root, right, 0, vardata2); - if (vardata1->rel && - bms_is_subset(vardata1->rel->relids, sjinfo->syn_righthand)) - *join_is_reversed = true; /* var1 is on RHS */ - else if (vardata2->rel && - bms_is_subset(vardata2->rel->relids, sjinfo->syn_lefthand)) - *join_is_reversed = true; /* var2 is on LHS */ - else - *join_is_reversed = false; + if (join_is_reversed) + *join_is_reversed = join_is_reversed_variables(sjinfo, vardata1, vardata2); } /* statext_expressions_load copies the tuple, so just pfree it. */ @@ -6402,7 +6799,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 38e943fab2b..bb2683f1665 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" @@ -2002,7 +2003,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; @@ -2032,6 +2033,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; @@ -2043,7 +2047,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) { @@ -2052,7 +2096,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 af000d4f488..82e332fbdd9 100644 --- a/src/backend/utils/cache/inval.c +++ b/src/backend/utils/cache/inval.c @@ -114,6 +114,7 @@ #include "access/xact.h" #include "access/xloginsert.h" #include "catalog/catalog.h" +#include "catalog/namespace.h" #include "catalog/pg_constraint.h" #include "miscadmin.h" #include "storage/sinval.h" @@ -394,12 +395,13 @@ AppendInvalidationMessageSubGroup(InvalidationMsgsGroup *dest, */ static void AddCatcacheInvalidationMessage(InvalidationMsgsGroup *group, - int id, uint32 hashValue, Oid dbId) + int id, uint32 hashValue, Oid dbId, bool isLocal) { SharedInvalidationMessage msg; Assert(id < CHAR_MAX); msg.cc.id = (int8) id; + msg.cc.isLocal = (int8) isLocal; msg.cc.dbId = dbId; msg.cc.hashValue = hashValue; @@ -440,7 +442,7 @@ AddCatalogInvalidationMessage(InvalidationMsgsGroup *group, */ static void AddRelcacheInvalidationMessage(InvalidationMsgsGroup *group, - Oid dbId, Oid relId) + Oid dbId, Oid relId, bool isLocal) { SharedInvalidationMessage msg; @@ -457,6 +459,7 @@ AddRelcacheInvalidationMessage(InvalidationMsgsGroup *group, /* OK, add the item */ msg.rc.id = SHAREDINVALRELCACHE_ID; + msg.rc.isLocal = isLocal; msg.rc.dbId = dbId; msg.rc.relId = relId; /* check AddCatcacheInvalidationMessage() for an explanation */ @@ -544,10 +547,10 @@ ProcessInvalidationMessagesMulti(InvalidationMsgsGroup *group, static void RegisterCatcacheInvalidation(int cacheId, uint32 hashValue, - Oid dbId) + Oid dbId, bool isLocal) { AddCatcacheInvalidationMessage(&transInvalInfo->CurrentCmdInvalidMsgs, - cacheId, hashValue, dbId); + cacheId, hashValue, dbId, isLocal); } /* @@ -568,10 +571,10 @@ RegisterCatalogInvalidation(Oid dbId, Oid catId) * As above, but register a relcache invalidation event. */ static void -RegisterRelcacheInvalidation(Oid dbId, Oid relId) +RegisterRelcacheInvalidation(Oid dbId, Oid relId, bool tempRel) { AddRelcacheInvalidationMessage(&transInvalInfo->CurrentCmdInvalidMsgs, - dbId, relId); + dbId, relId, tempRel); /* * Most of the time, relcache invalidation is associated with system @@ -1211,6 +1214,8 @@ CacheInvalidateHeapTuple(Relation relation, Oid tupleRelId; Oid databaseId; Oid relationId; + bool tempRel = false; + bool checkTemp = false; /* Do nothing during bootstrap */ if (IsBootstrapProcessingMode()) @@ -1266,6 +1271,9 @@ CacheInvalidateHeapTuple(Relation relation, databaseId = InvalidOid; else databaseId = MyDatabaseId; + + tempRel = (classtup->relpersistence == RELPERSISTENCE_TEMP) ? + true : false; } else if (tupleRelId == AttributeRelationId) { @@ -1284,6 +1292,7 @@ CacheInvalidateHeapTuple(Relation relation, * never come here for a shared rel anyway.) */ databaseId = MyDatabaseId; + checkTemp = true; } else if (tupleRelId == IndexRelationId) { @@ -1297,6 +1306,7 @@ CacheInvalidateHeapTuple(Relation relation, */ relationId = indextup->indexrelid; databaseId = MyDatabaseId; + checkTemp = true; } else if (tupleRelId == ConstraintRelationId) { @@ -1318,10 +1328,27 @@ CacheInvalidateHeapTuple(Relation relation, else return; + if (checkTemp) + { + HeapTuple htup = SearchSysCache1(RELOID, ObjectIdGetDatum(relationId)); + + if (HeapTupleIsValid(htup)) + { + Form_pg_class c = (Form_pg_class)GETSTRUCT(htup); + + tempRel = (c->relisshared == false && + c->relpersistence == RELPERSISTENCE_TEMP && + isTempOrTempToastNamespace(c->relnamespace)) ? + true : false; + + ReleaseSysCache(htup); + } + } + /* * Yes. We need to register a relcache invalidation event. */ - RegisterRelcacheInvalidation(databaseId, relationId); + RegisterRelcacheInvalidation(databaseId, relationId, tempRel); } /* @@ -1373,7 +1400,10 @@ CacheInvalidateRelcache(Relation relation) else databaseId = MyDatabaseId; - RegisterRelcacheInvalidation(databaseId, relationId); + RegisterRelcacheInvalidation(databaseId, relationId, + relation->rd_rel->relisshared == false && + RELATION_IS_LOCAL(relation) && + !RELATION_IS_OTHER_TEMP(relation)); } /* @@ -1388,7 +1418,7 @@ CacheInvalidateRelcacheAll(void) { PrepareInvalidationState(); - RegisterRelcacheInvalidation(InvalidOid, InvalidOid); + RegisterRelcacheInvalidation(InvalidOid, InvalidOid, false); } /* @@ -1409,7 +1439,10 @@ CacheInvalidateRelcacheByTuple(HeapTuple classTuple) databaseId = InvalidOid; else databaseId = MyDatabaseId; - RegisterRelcacheInvalidation(databaseId, relationId); + RegisterRelcacheInvalidation(databaseId, relationId, + classtup->relisshared == false && + classtup->relpersistence == RELPERSISTENCE_TEMP && + isTempOrTempToastNamespace(classtup->relnamespace)); } /* diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c index 1b7e11b93e0..597a477565b 100644 --- a/src/backend/utils/cache/lsyscache.c +++ b/src/backend/utils/cache/lsyscache.c @@ -43,6 +43,7 @@ #include "utils/datum.h" #include "utils/fmgroids.h" #include "utils/lsyscache.h" +#include "utils/memutils.h" #include "utils/rel.h" #include "utils/syscache.h" #include "utils/typcache.h" @@ -3296,6 +3297,52 @@ get_attstatsslot(AttStatsSlot *sslot, HeapTuple statstuple, return true; } +AttStatsSlot* +fill_attstatsslot(AttStatsSlot *sslots, HeapTuple statstuple, + int reqkind, Oid reqop, int flags) +{ + int add_flags = 0, has_flags = 0; + AttStatsSlot *sslot; + MemoryContext oldctx; + + if (reqkind >= STATISTIC_NUM_SLOTS) + return NULL; /* not there */ + + sslot = sslots + reqkind; + + if (sslot->values != NULL) + has_flags |= ATTSTATSSLOT_VALUES; + if (sslot->numbers != NULL) + has_flags |= ATTSTATSSLOT_NUMBERS; + + if ((flags & ATTSTATSSLOT_VALUES) && !(has_flags & ATTSTATSSLOT_VALUES)) + add_flags |= ATTSTATSSLOT_VALUES; + + if ((flags & ATTSTATSSLOT_NUMBERS) && !(has_flags & ATTSTATSSLOT_NUMBERS)) + add_flags |= ATTSTATSSLOT_NUMBERS; + + if (add_flags == 0 && (reqop == InvalidOid || sslot->staop == reqop)) + return sslot; + + sslot->incache = false; + free_attstatsslot(sslot); + + oldctx = MemoryContextSwitchTo(GetMemoryChunkContext(sslots)); + + if (get_attstatsslot(sslot, statstuple, reqkind, reqop, + add_flags | has_flags)) + { + sslot->incache = true; + MemoryContextSwitchTo(oldctx); + return sslot; + } + else + { + MemoryContextSwitchTo(oldctx); + return NULL; + } +} + /* * free_attstatsslot * Free data allocated by get_attstatsslot @@ -3303,6 +3350,10 @@ get_attstatsslot(AttStatsSlot *sslot, HeapTuple statstuple, void free_attstatsslot(AttStatsSlot *sslot) { + /* do not free cached slot */ + if (sslot->incache) + return; + /* The values[] array was separately palloc'd by deconstruct_array */ if (sslot->values) pfree(sslot->values); diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c index 62cd300453c..3e7ef8c273d 100644 --- a/src/backend/utils/cache/typcache.c +++ b/src/backend/utils/cache/typcache.c @@ -77,6 +77,13 @@ /* The main type cache hashtable searched by lookup_type_cache */ static HTAB *TypeCacheHash = NULL; +typedef struct mapRelTypeEntry +{ + Oid typrelid; + Oid type_id; +} mapRelTypeEntry; +static HTAB *mapRelType = NULL; + /* List of type cache entries for domain types */ static TypeCacheEntry *firstDomainTypeEntry = NULL; @@ -328,6 +335,15 @@ static TupleDesc find_or_make_matching_shared_tupledesc(TupleDesc tupdesc); static dsa_pointer share_tupledesc(dsa_area *area, TupleDesc tupdesc, uint32 typmod); +/* + * Hashing function should compatible with syscache hashing function to use + * hash_seq_init_with_hash_value() + */ +static uint32 +type_cache_hash(const void *key, Size keysize) +{ + return GetSysCacheHashValue1(TYPEOID, ObjectIdGetDatum(*(const Oid*)key)); +} /* * lookup_type_cache @@ -353,8 +369,14 @@ lookup_type_cache(Oid type_id, int flags) ctl.keysize = sizeof(Oid); ctl.entrysize = sizeof(TypeCacheEntry); + ctl.hash = type_cache_hash; TypeCacheHash = hash_create("Type information cache", 64, - &ctl, HASH_ELEM | HASH_BLOBS); + &ctl, HASH_ELEM | HASH_FUNCTION); + + ctl.keysize = sizeof(Oid); + ctl.entrysize = sizeof(mapRelTypeEntry); + mapRelType = hash_create("Map reloid to typeoid", 64, + &ctl, HASH_ELEM | HASH_BLOBS); /* Also set up callbacks for SI invalidations */ CacheRegisterRelcacheCallback(TypeCacheRelCallback, (Datum) 0); @@ -405,8 +427,7 @@ lookup_type_cache(Oid type_id, int flags) /* These fields can never change, by definition */ typentry->type_id = type_id; - typentry->type_id_hash = GetSysCacheHashValue1(TYPEOID, - ObjectIdGetDatum(type_id)); + typentry->type_id_hash = get_hash_value(TypeCacheHash, &type_id); /* Keep this part in sync with the code below */ typentry->typlen = typtup->typlen; @@ -427,6 +448,18 @@ lookup_type_cache(Oid type_id, int flags) firstDomainTypeEntry = typentry; } + if (OidIsValid(typtup->typrelid)) + { + mapRelTypeEntry *relentry; + + relentry = (mapRelTypeEntry*) hash_search(mapRelType, + &typentry->typrelid, + HASH_ENTER, NULL); + + relentry->typrelid = typentry->typrelid; + relentry->type_id = typentry->type_id; + } + ReleaseSysCache(tp); } else if (!(typentry->flags & TCFLAGS_HAVE_PG_TYPE_DATA)) @@ -465,6 +498,18 @@ lookup_type_cache(Oid type_id, int flags) typentry->typcollation = typtup->typcollation; typentry->flags |= TCFLAGS_HAVE_PG_TYPE_DATA; + if (OidIsValid(typtup->typrelid)) + { + mapRelTypeEntry *relentry; + + relentry = (mapRelTypeEntry*) hash_search(mapRelType, + &typentry->typrelid, + HASH_ENTER, NULL); + + relentry->typrelid = typentry->typrelid; + relentry->type_id = typentry->type_id; + } + ReleaseSysCache(tp); } @@ -2286,58 +2331,72 @@ SharedRecordTypmodRegistryAttach(SharedRecordTypmodRegistry *registry) static void TypeCacheRelCallback(Datum arg, Oid relid) { - HASH_SEQ_STATUS status; TypeCacheEntry *typentry; /* TypeCacheHash must exist, else this callback wouldn't be registered */ - hash_seq_init(&status, TypeCacheHash); - while ((typentry = (TypeCacheEntry *) hash_seq_search(&status)) != NULL) + + if (OidIsValid(relid)) { - if (typentry->typtype == TYPTYPE_COMPOSITE) + mapRelTypeEntry *relentry; + + relentry = (mapRelTypeEntry *) hash_search(mapRelType, + &relid, + HASH_FIND, NULL); + + if (relentry != NULL) { - /* Skip if no match, unless we're zapping all composite types */ - if (relid != typentry->typrelid && relid != InvalidOid) - continue; + typentry = (TypeCacheEntry *) hash_search(TypeCacheHash, + &relentry->type_id, + HASH_FIND, NULL); - /* Delete tupdesc if we have it */ - if (typentry->tupDesc != NULL) + if (typentry != NULL) { - /* - * Release our refcount, and free the tupdesc if none remain. - * (Can't use DecrTupleDescRefCount because this reference is - * not logged in current resource owner.) - */ - Assert(typentry->tupDesc->tdrefcount > 0); - if (--typentry->tupDesc->tdrefcount == 0) - FreeTupleDesc(typentry->tupDesc); - typentry->tupDesc = NULL; - - /* - * Also clear tupDesc_identifier, so that anything watching - * that will realize that the tupdesc has possibly changed. - * (Alternatively, we could specify that to detect possible - * tupdesc change, one must check for tupDesc != NULL as well - * as tupDesc_identifier being the same as what was previously - * seen. That seems error-prone.) - */ - typentry->tupDesc_identifier = 0; - } + Assert(typentry->typtype == TYPTYPE_COMPOSITE); + Assert(relid == typentry->typrelid); - /* Reset equality/comparison/hashing validity information */ - typentry->flags &= ~TCFLAGS_OPERATOR_FLAGS; - } - else if (typentry->typtype == TYPTYPE_DOMAIN) - { - /* - * If it's domain over composite, reset flags. (We don't bother - * trying to determine whether the specific base type needs a - * reset.) Note that if we haven't determined whether the base - * type is composite, we don't need to reset anything. - */ - if (typentry->flags & TCFLAGS_DOMAIN_BASE_IS_COMPOSITE) + /* Delete tupdesc if we have it */ + if (typentry->tupDesc != NULL) + { + /* + * Release our refcount, and free the tupdesc if none remain. + * (Can't use DecrTupleDescRefCount because this reference is + * not logged in current resource owner.) + */ + Assert(typentry->tupDesc->tdrefcount > 0); + if (--typentry->tupDesc->tdrefcount == 0) + FreeTupleDesc(typentry->tupDesc); + typentry->tupDesc = NULL; + + /* + * Also clear tupDesc_identifier, so that anything watching + * that will realize that the tupdesc has possibly changed. + * (Alternatively, we could specify that to detect possible + * tupdesc change, one must check for tupDesc != NULL as well + * as tupDesc_identifier being the same as what was previously + * seen. That seems error-prone.) + */ + typentry->tupDesc_identifier = 0; + } + + /* Reset equality/comparison/hashing validity information */ typentry->flags &= ~TCFLAGS_OPERATOR_FLAGS; + } } } + + for (typentry = firstDomainTypeEntry; + typentry != NULL; + typentry = typentry->nextDomain) + { + /* + * If it's domain over composite, reset flags. (We don't bother + * trying to determine whether the specific base type needs a + * reset.) Note that if we haven't determined whether the base + * type is composite, we don't need to reset anything. + */ + if (typentry->flags & TCFLAGS_DOMAIN_BASE_IS_COMPOSITE) + typentry->flags &= ~TCFLAGS_OPERATOR_FLAGS; + } } /* @@ -2355,20 +2414,20 @@ TypeCacheTypCallback(Datum arg, int cacheid, uint32 hashvalue) TypeCacheEntry *typentry; /* TypeCacheHash must exist, else this callback wouldn't be registered */ - hash_seq_init(&status, TypeCacheHash); + if (hashvalue == 0) + hash_seq_init(&status, TypeCacheHash); + else + hash_seq_init_with_hash_value(&status, TypeCacheHash, hashvalue); + while ((typentry = (TypeCacheEntry *) hash_seq_search(&status)) != NULL) { - /* Is this the targeted type row (or it's a total cache flush)? */ - if (hashvalue == 0 || typentry->type_id_hash == hashvalue) - { - /* - * Mark the data obtained directly from pg_type as invalid. Also, - * if it's a domain, typnotnull might've changed, so we'll need to - * recalculate its constraints. - */ - typentry->flags &= ~(TCFLAGS_HAVE_PG_TYPE_DATA | - TCFLAGS_CHECKED_DOMAIN_CONSTRAINTS); - } + /* + * Mark the data obtained directly from pg_type as invalid. Also, + * if it's a domain, typnotnull might've changed, so we'll need to + * recalculate its constraints. + */ + typentry->flags &= ~(TCFLAGS_HAVE_PG_TYPE_DATA | + TCFLAGS_CHECKED_DOMAIN_CONSTRAINTS); } } diff --git a/src/backend/utils/hash/dynahash.c b/src/backend/utils/hash/dynahash.c index 3babde8d704..ea026061059 100644 --- a/src/backend/utils/hash/dynahash.c +++ b/src/backend/utils/hash/dynahash.c @@ -1428,10 +1428,37 @@ hash_seq_init(HASH_SEQ_STATUS *status, HTAB *hashp) status->hashp = hashp; status->curBucket = 0; status->curEntry = NULL; + status->hasHashVal = false; if (!hashp->frozen) register_seq_scan(hashp); } +void +hash_seq_init_with_hash_value(HASH_SEQ_STATUS *status, HTAB *hashp, + uint32 hashvalue) +{ + HASHHDR *hctl = hashp->hctl; + long segment_num; + long segment_ndx; + HASHSEGMENT segp; + + hash_seq_init(status, hashp); + status->hasHashVal = true; + status->hashvalue = hashvalue; + + status->curBucket = calc_bucket(hctl, hashvalue); + + segment_num = status->curBucket >> hashp->sshift; + segment_ndx = MOD(status->curBucket, hashp->ssize); + + segp = hashp->dir[segment_num]; + + if (segp == NULL) + hash_corrupted(hashp); + + status->curEntry = segp[segment_ndx]; +} + void * hash_seq_search(HASH_SEQ_STATUS *status) { @@ -1445,6 +1472,20 @@ hash_seq_search(HASH_SEQ_STATUS *status) uint32 curBucket; HASHELEMENT *curElem; + if (status->hasHashVal) + { + while ((curElem = status->curEntry) != NULL) + { + status->curEntry = curElem->link; + if (status->hashvalue != curElem->hashvalue) + continue; + return (void *) ELEMENTKEY(curElem); + } + + hash_seq_term(status); + return NULL; + } + if ((curElem = status->curEntry) != NULL) { /* Continuing scan of curBucket... */ diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index 0b5b77b0003..132931cd1f9 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -143,6 +143,7 @@ extern char *temp_tablespaces; extern bool ignore_checksum_failure; extern bool ignore_invalid_pages; extern bool synchronize_seqscans; +extern bool enable_self_join_removal; #ifdef TRACE_SYNCSCAN extern bool trace_syncscan; @@ -242,6 +243,7 @@ static bool check_recovery_target_lsn(char **newval, void **extra, GucSource sou static void assign_recovery_target_lsn(const char *newval, void *extra); static bool check_primary_slot_name(char **newval, void **extra, GucSource source); static bool check_default_with_oids(bool *newval, void **extra, GucSource source); +static void assign_default_with_oids(bool newval, void *extra); /* Private functions in guc-file.l that need to be called from guc.c */ static ConfigVariable *ProcessConfigFileInternal(GucContext context, @@ -1214,6 +1216,26 @@ static struct config_bool ConfigureNamesBool[] = true, NULL, NULL, NULL }, + { + {"enable_self_join_removal", PGC_USERSET, QUERY_TUNING_METHOD, + gettext_noop("Enable removal of unique self-joins."), + NULL, + GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE + }, + &enable_self_join_removal, + true, + NULL, NULL, NULL + }, + { + {"enable_group_by_reordering", PGC_USERSET, QUERY_TUNING_METHOD, + gettext_noop("Enables reordering of GROUP BY keys."), + NULL, + GUC_EXPLAIN + }, + &enable_group_by_reordering, + true, + NULL, NULL, NULL + }, { {"geqo", PGC_USERSET, QUERY_TUNING_GEQO, gettext_noop("Enables genetic query optimization."), @@ -1810,7 +1832,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, @@ -12991,13 +13013,19 @@ check_default_with_oids(bool *newval, void **extra, GucSource source) if (*newval) { /* check the GUC's definition for an explanation */ - GUC_check_errcode(ERRCODE_FEATURE_NOT_SUPPORTED); - GUC_check_errmsg("tables declared WITH OIDS are not supported"); + ereport(WARNING, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("tables declared WITH OIDS are not supported, ignored"))); - return false; + *newval = false; } return true; } +static void +assign_default_with_oids(bool newval, void *extra) +{ +} + #include "guc-file.c" diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample index f92ff4cc295..b4bc06e5f5a 100644 --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -388,6 +388,7 @@ #enable_seqscan = on #enable_sort = on #enable_tidscan = on +#enable_group_by_reordering = on # - Planner Cost Constants - diff --git a/src/backend/utils/mmgr/aset.c b/src/backend/utils/mmgr/aset.c index 81117104f9e..93359ba09e0 100644 --- a/src/backend/utils/mmgr/aset.c +++ b/src/backend/utils/mmgr/aset.c @@ -1474,7 +1474,6 @@ AllocSetCheck(MemoryContext context) chsize = chunk->size; /* aligned chunk size */ dsize = chunk->requested_size; /* real data */ - /* * Check chunk size */ diff --git a/src/backend/utils/sort/tuplestore.c b/src/backend/utils/sort/tuplestore.c index f605ece721e..dfc8a6d8109 100644 --- a/src/backend/utils/sort/tuplestore.c +++ b/src/backend/utils/sort/tuplestore.c @@ -545,7 +545,7 @@ tuplestore_select_read_pointer(Tuplestorestate *state, int ptr) int64 tuplestore_tuple_count(Tuplestorestate *state) { - return state->tuples; + return (state) ? state->tuples : 0; } /* diff --git a/src/bin/pg_basebackup/pg_receivewal.c b/src/bin/pg_basebackup/pg_receivewal.c index 26dcf339f72..8897fff7b34 100644 --- a/src/bin/pg_basebackup/pg_receivewal.c +++ b/src/bin/pg_basebackup/pg_receivewal.c @@ -99,6 +99,7 @@ usage(void) printf(_(" -d, --dbname=CONNSTR connection string\n")); printf(_(" -h, --host=HOSTNAME database server host or socket directory\n")); printf(_(" -p, --port=PORT database server port number\n")); + printf(_(" -u, --umask set files mode according to umask (might break security!)\n")); printf(_(" -U, --username=NAME connect as specified database user\n")); printf(_(" -w, --no-password never prompt for password\n")); printf(_(" -W, --password force password prompt (should happen automatically)\n")); @@ -696,6 +697,7 @@ main(int argc, char **argv) {"endpos", required_argument, NULL, 'E'}, {"host", required_argument, NULL, 'h'}, {"port", required_argument, NULL, 'p'}, + {"umask", no_argument, NULL, 'u'}, {"username", required_argument, NULL, 'U'}, {"no-loop", no_argument, NULL, 'n'}, {"no-password", no_argument, NULL, 'w'}, @@ -742,7 +744,7 @@ main(int argc, char **argv) } } - while ((c = getopt_long(argc, argv, "D:d:E:h:p:U:s:S:nwWvZ:", + while ((c = getopt_long(argc, argv, "D:d:E:h:p:U:s:S:nuwWvZ:", long_options, &option_index)) != -1) { switch (c) @@ -759,6 +761,9 @@ main(int argc, char **argv) case 'p': dbport = pg_strdup(optarg); break; + case 'u': + useumask = 1; + break; case 'U': dbuser = pg_strdup(optarg); break; diff --git a/src/bin/pg_basebackup/pg_recvlogical.c b/src/bin/pg_basebackup/pg_recvlogical.c index 02b8e272c37..d4ee0dd879c 100644 --- a/src/bin/pg_basebackup/pg_recvlogical.c +++ b/src/bin/pg_basebackup/pg_recvlogical.c @@ -325,11 +325,14 @@ StreamLogicalLog(void) { struct stat statbuf; + mode_t mode = (useumask == 1) ? + (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) : (S_IRUSR | S_IWUSR); + if (strcmp(outfile, "-") == 0) outfd = fileno(stdout); else outfd = open(outfile, O_CREAT | O_APPEND | O_WRONLY | PG_BINARY, - S_IRUSR | S_IWUSR); + mode); if (outfd == -1) { pg_log_error("could not open log file \"%s\": %m", outfile); diff --git a/src/bin/pg_basebackup/streamutil.c b/src/bin/pg_basebackup/streamutil.c index 1478aa9f25a..6df1904b4a1 100644 --- a/src/bin/pg_basebackup/streamutil.c +++ b/src/bin/pg_basebackup/streamutil.c @@ -49,6 +49,7 @@ char *dbhost = NULL; char *dbuser = NULL; char *dbport = NULL; char *dbname = NULL; +int useumask = 0; /* 0=auto, -1=never, 1=always */ int dbgetpassword = 0; /* 0=auto, -1=never, 1=always */ static char *password = NULL; PGconn *conn = NULL; diff --git a/src/bin/pg_basebackup/streamutil.h b/src/bin/pg_basebackup/streamutil.h index 8638f81f3bf..9104d1ccd68 100644 --- a/src/bin/pg_basebackup/streamutil.h +++ b/src/bin/pg_basebackup/streamutil.h @@ -23,6 +23,7 @@ extern char *dbhost; extern char *dbuser; extern char *dbport; extern char *dbname; +extern int useumask; extern int dbgetpassword; extern uint32 WalSegSz; diff --git a/src/bin/pg_basebackup/walmethods.c b/src/bin/pg_basebackup/walmethods.c index 602727f4d43..f2db6796df8 100644 --- a/src/bin/pg_basebackup/walmethods.c +++ b/src/bin/pg_basebackup/walmethods.c @@ -119,6 +119,8 @@ dir_open_for_write(const char *pathname, const char *temp_suffix, size_t pad_to_ size_t lz4bufsize = 0; void *lz4buf = NULL; #endif + mode_t mode = (useumask == 1) ? + (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) : (S_IRUSR | S_IWUSR); dir_clear_error(); @@ -133,7 +135,7 @@ dir_open_for_write(const char *pathname, const char *temp_suffix, size_t pad_to_ * does not do any system calls to fsync() to make changes permanent on * disk. */ - fd = open(tmppath, O_WRONLY | O_CREAT | PG_BINARY, pg_file_create_mode); + fd = open(tmppath, O_WRONLY | O_CREAT | PG_BINARY, pg_file_create_mode | mode); if (fd < 0) { dir_data->lasterrno = errno; @@ -828,6 +830,8 @@ tar_get_file_name(const char *pathname, const char *temp_suffix) static Walfile tar_open_for_write(const char *pathname, const char *temp_suffix, size_t pad_to_size) { + mode_t mode = (useumask == 1) ? + (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) : (S_IRUSR | S_IWUSR); char *tmppath; tar_clear_error(); @@ -839,7 +843,7 @@ tar_open_for_write(const char *pathname, const char *temp_suffix, size_t pad_to_ */ tar_data->fd = open(tar_data->tarfilename, O_WRONLY | O_CREAT | PG_BINARY, - pg_file_create_mode); + pg_file_create_mode | mode); if (tar_data->fd < 0) { tar_data->lasterrno = errno; diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c index a64d37e5937..74421d6c343 100644 --- a/src/bin/pg_dump/common.c +++ b/src/bin/pg_dump/common.c @@ -86,7 +86,6 @@ static void flagInhAttrs(DumpOptions *dopt, TableInfo *tblinfo, int numTables); static void findParentsByOid(TableInfo *self, InhInfo *inhinfo, int numInherits); static int strInArray(const char *pattern, char **arr, int arr_size); -static IndxInfo *findIndexByOid(Oid oid); /* @@ -783,7 +782,7 @@ findTableByOid(Oid oid) * finds the DumpableObject for the index with the given oid * returns NULL if not found */ -static IndxInfo * +IndxInfo * findIndexByOid(Oid oid) { CatalogId catId; diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 5e7317bd7a3..0b7b46b1702 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -1742,11 +1742,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; @@ -4784,6 +4795,9 @@ binary_upgrade_set_type_oids_by_type_oid(Archive *fout, Oid pg_type_multirange_oid; Oid pg_type_multirange_array_oid; + if (pg_type_oid == InvalidOid) + return; + appendPQExpBufferStr(upgrade_buffer, "\n-- For binary upgrade, must preserve pg_type oid\n"); appendPQExpBuffer(upgrade_buffer, "SELECT pg_catalog.binary_upgrade_set_next_pg_type_oid('%u'::pg_catalog.oid);\n\n", @@ -4868,6 +4882,17 @@ binary_upgrade_set_type_oids_by_rel(Archive *fout, pg_type_oid, false, false); } +static void +binary_upgrade_set_type_oids_by_rel_oid(Archive *fout, + PQExpBuffer upgrade_buffer, + Oid pg_type_oid + ) +{ + if (OidIsValid(pg_type_oid)) + binary_upgrade_set_type_oids_by_type_oid(fout, upgrade_buffer, + pg_type_oid, false, false); +} + static void binary_upgrade_set_pg_class_oids(Archive *fout, PQExpBuffer upgrade_buffer, Oid pg_class_oid, @@ -6790,6 +6815,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables) i_indnkeyatts, i_indnatts, i_indkey, + i_indtype, i_indisclustered, i_indisreplident, i_indnullsnotdistinct, @@ -6838,7 +6864,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables) "SELECT t.tableoid, t.oid, i.indrelid, " "t.relname AS indexname, " "pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, " - "i.indkey, i.indisclustered, " + "i.indkey, t.reltype AS indtype, i.indisclustered, " "c.contype, c.conname, " "c.condeferrable, c.condeferred, " "c.tableoid AS contableoid, " @@ -6942,6 +6968,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables) i_indnkeyatts = PQfnumber(res, "indnkeyatts"); i_indnatts = PQfnumber(res, "indnatts"); i_indkey = PQfnumber(res, "indkey"); + i_indtype = PQfnumber(res, "indtype"); i_indisclustered = PQfnumber(res, "indisclustered"); i_indisreplident = PQfnumber(res, "indisreplident"); i_indnullsnotdistinct = PQfnumber(res, "indnullsnotdistinct"); @@ -7019,6 +7046,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables) indxinfo[j].indkeys = (Oid *) pg_malloc(indxinfo[j].indnattrs * sizeof(Oid)); parseOidArray(PQgetvalue(res, j, i_indkey), indxinfo[j].indkeys, indxinfo[j].indnattrs); + indxinfo[j].indtype = atooid(PQgetvalue(res, j, i_indtype)); indxinfo[j].indisclustered = (PQgetvalue(res, j, i_indisclustered)[0] == 't'); indxinfo[j].indisreplident = (PQgetvalue(res, j, i_indisreplident)[0] == 't'); indxinfo[j].indnullsnotdistinct = (PQgetvalue(res, j, i_indnullsnotdistinct)[0] == 't'); @@ -16238,8 +16266,13 @@ dumpIndex(Archive *fout, const IndxInfo *indxinfo) int nstatvals = 0; if (dopt->binary_upgrade) + { binary_upgrade_set_pg_class_oids(fout, q, indxinfo->dobj.catId.oid, true); + if (indxinfo->indnkeyattrs > 1) + binary_upgrade_set_type_oids_by_rel_oid(fout, q, + indxinfo->indtype); + } /* Plain secondary index */ appendPQExpBuffer(q, "%s;\n", indxinfo->indexdef); @@ -16495,8 +16528,14 @@ dumpConstraint(Archive *fout, const ConstraintInfo *coninfo) coninfo->dobj.name); if (dopt->binary_upgrade) + { + if (indxinfo->indnkeyattrs > 1) + binary_upgrade_set_type_oids_by_rel_oid(fout, q, + indxinfo->indtype); + binary_upgrade_set_pg_class_oids(fout, q, indxinfo->dobj.catId.oid, true); + } appendPQExpBuffer(q, "ALTER %sTABLE ONLY %s\n", foreign, fmtQualifiedDumpable(tbinfo)); @@ -18037,6 +18076,28 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs, */ switch (dobj->objType) { + case DO_DUMMY_TYPE: + { + /* + * In Vanilla, dummy types were only created for tables. + * In Postgres Pro for improving join selectivity estimation + * we also create two types for each composite index: + * 1) a type for attributes of the index + * 2) a type which is an array containing elements of type (1) + * These types depend on indexes, so adding preDataBound -> type + * dependency would create a loop; don't do that. + */ + TypeInfo *tyinfo = (TypeInfo *) dobj; + if (tyinfo->isArray) + /* If it's an array, take its element type */ + tyinfo = findTypeByOid(tyinfo->typelem); + + if (OidIsValid(tyinfo->typrelid) && + (tyinfo->typrelkind == RELKIND_INDEX || + tyinfo->typrelkind == RELKIND_PARTITIONED_INDEX)) + break; + } + /* FALLTHROUGH */ case DO_NAMESPACE: case DO_EXTENSION: case DO_TYPE: @@ -18054,7 +18115,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 0cdcc010317..3d04e220e29 100644 --- a/src/bin/pg_dump/pg_dump.h +++ b/src/bin/pg_dump/pg_dump.h @@ -398,6 +398,7 @@ typedef struct _indxInfo int indnattrs; /* total number of index attributes */ Oid *indkeys; /* In spite of the name 'indkeys' this field * contains both key and nonkey attributes */ + Oid indtype; /* OID of index's composite type, if any */ bool indisclustered; bool indisreplident; bool indnullsnotdistinct; @@ -688,6 +689,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 recordExtensionMembership(CatalogId catId, ExtensionInfo *ext); diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h index a1d6e3b645f..40197eb0422 100644 --- a/src/include/catalog/index.h +++ b/src/include/catalog/index.h @@ -65,6 +65,7 @@ extern void index_check_primary_key(Relation heapRel, #define INDEX_CREATE_IF_NOT_EXISTS (1 << 4) #define INDEX_CREATE_PARTITIONED (1 << 5) #define INDEX_CREATE_INVALID (1 << 6) +#define INDEX_CREATE_WITHOUT_TYPE (1 << 7) extern Oid index_create(Relation heapRelation, const char *indexRelationName, diff --git a/src/include/catalog/pg_type.dat b/src/include/catalog/pg_type.dat index df458794635..8b783e5efe8 100644 --- a/src/include/catalog/pg_type.dat +++ b/src/include/catalog/pg_type.dat @@ -692,4 +692,12 @@ typreceive => 'brin_minmax_multi_summary_recv', typsend => 'brin_minmax_multi_summary_send', typalign => 'i', typstorage => 'x', typcollation => 'default' }, +{ oid => '14756', descr => 'pseudo-type representing removed abstime', + typname => 'abstime', typlen => '-1', typbyval => 'f', typtype => 'p', + typcategory => 'P', typinput => 'timestamp_in', typoutput => 'timestamp_out', + typreceive => 'timestamp_recv', typsend => 'timestamp_send', typalign => 'c' }, +{ oid => '14757', descr => 'pseudo-type representing removed reltime', + typname => 'reltime', typlen => '-1', typbyval => 'f', typtype => 'p', + typcategory => 'P', typinput => 'timestamp_in', typoutput => 'timestamp_out', + typreceive => 'timestamp_recv', typsend => 'timestamp_send', typalign => 'c' }, ] diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h index 144728e23d3..220261ab395 100644 --- a/src/include/executor/execExpr.h +++ b/src/include/executor/execExpr.h @@ -351,12 +351,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; @@ -617,6 +622,7 @@ typedef struct ExprEvalStep { /* out-of-line state, created by nodeSubplan.c */ SubPlanState *sstate; + bool *guaranteed_empty; } subplan; /* for EEOP_AGG_*DESERIALIZE */ diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 9f176b0e371..2f3d20d06f6 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -114,6 +114,8 @@ typedef struct ExprState Datum *innermost_domainval; bool *innermost_domainnull; + + bool guaranteed_empty; } ExprState; @@ -964,6 +966,7 @@ typedef struct SubPlanState FmgrInfo *lhs_hash_funcs; /* hash functions for lefthand datatype(s) */ FmgrInfo *cur_eq_funcs; /* equality functions for LHS vs. table */ ExprState *cur_eq_comp; /* equality comparator for LHS vs. table */ + bool guaranteed_empty; } SubPlanState; /* @@ -1069,6 +1072,8 @@ typedef struct PlanState */ TupleDesc scandesc; + bool guaranteed_empty; + /* * Define the slot types for inner, outer and scanslots for expression * contexts with this state as a parent. If *opsset is set, then diff --git a/src/include/nodes/nodeFuncs.h b/src/include/nodes/nodeFuncs.h index 93c60bde667..41327813976 100644 --- a/src/include/nodes/nodeFuncs.h +++ b/src/include/nodes/nodeFuncs.h @@ -28,6 +28,7 @@ * contents */ #define QTW_DONT_COPY_QUERY 0x40 /* do not copy top Query */ #define QTW_EXAMINE_SORTGROUP 0x80 /* include SortGroupNode lists */ +#define QTW_DONT_COPY_DEFAULT 0x00 /* only custom mutator will copy */ /* callback function for check_functions_in_node */ typedef bool (*check_function_callback) (Oid func_id, void *context); @@ -132,7 +133,7 @@ extern bool check_functions_in_node(Node *node, check_function_callback checker, extern bool expression_tree_walker(Node *node, bool (*walker) (), void *context); extern Node *expression_tree_mutator(Node *node, Node *(*mutator) (), - void *context); + void *context, int flags); extern bool query_tree_walker(Query *query, bool (*walker) (), void *context, int flags); diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index 9d804d4b321..5390e6ada27 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -15,10 +15,12 @@ #define PATHNODES_H #include "access/sdir.h" +#include "catalog/pg_statistic.h" #include "lib/stringinfo.h" #include "nodes/params.h" #include "nodes/parsenodes.h" #include "storage/block.h" +#include "utils/lsyscache.h" /* @@ -888,6 +890,10 @@ struct IndexOptInfo bool amcanmarkpos; /* does AM support mark/restore? */ /* Rather than include amapi.h here, we declare amcostestimate like this */ void (*amcostestimate) (); /* AM's cost estimator */ + + /* cache for per-tuple index statistic. That stats could be large and it + * will be expensive to uncompress it every time */ + AttStatsSlot *sslots; }; /* @@ -1071,6 +1077,16 @@ typedef struct PathKey bool pk_nulls_first; /* do NULLs come before normal values? */ } PathKey; +/* + * Combines information about pathkeys and the associated clauses. + */ +typedef struct PathKeyInfo +{ + NodeTag type; + List *pathkeys; + List *clauses; +} PathKeyInfo; + /* * VolatileFunctionStatus -- allows nodes to cache their * contain_volatile_functions properties. VOLATILITY_UNKNOWN means not yet @@ -1458,6 +1474,11 @@ typedef struct AppendPath /* Index of first partial path in subpaths; list_length(subpaths) if none */ int first_partial_path; Cardinality limit_tuples; /* hard limit on output tuples, or -1 */ + bool pull_tlist; /* if = true, create_append_plan() + * should get targetlist from any + * subpath - they are the same, + * because the only place - append + * index scan for range OR */ } AppendPath; #define IS_DUMMY_APPEND(p) \ diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h index b778ba99134..0caec135ebb 100644 --- a/src/include/nodes/plannodes.h +++ b/src/include/nodes/plannodes.h @@ -21,6 +21,7 @@ #include "nodes/lockoptions.h" #include "nodes/parsenodes.h" #include "nodes/primnodes.h" +#include "portability/instr_time.h" /* ---------------------------------------------------------------- @@ -86,6 +87,8 @@ typedef struct PlannedStmt Node *utilityStmt; /* non-null if this is utility stmt */ + instr_time planDuration; /* time spent on planning */ + /* statement location in source string (copied from Query) */ int stmt_location; /* start location, or -1 if unknown */ int stmt_len; /* length in bytes; 0 means "rest of string" */ diff --git a/src/include/nodes/supportnodes.h b/src/include/nodes/supportnodes.h index 9fcbc399495..aba687f89e8 100644 --- a/src/include/nodes/supportnodes.h +++ b/src/include/nodes/supportnodes.h @@ -35,6 +35,22 @@ #include "nodes/plannodes.h" +typedef enum +{ + Pattern_Type_Like, + Pattern_Type_Like_IC, + Pattern_Type_Regex, + Pattern_Type_Regex_IC, + Pattern_Type_Prefix +} Pattern_Type; + +typedef enum +{ + Pattern_Prefix_None, + Pattern_Prefix_Partial, + Pattern_Prefix_Exact +} Pattern_Prefix_Status; + struct PlannerInfo; /* avoid including pathnodes.h here */ struct IndexOptInfo; struct SpecialJoinInfo; diff --git a/src/include/optimizer/cost.h b/src/include/optimizer/cost.h index bc12071af6e..0fd3a1158a5 100644 --- a/src/include/optimizer/cost.h +++ b/src/include/optimizer/cost.h @@ -114,7 +114,9 @@ extern void cost_incremental_sort(Path *path, Cost input_startup_cost, Cost input_total_cost, double input_tuples, int width, Cost comparison_cost, int sort_mem, double limit_tuples); -extern void cost_append(AppendPath *path); +extern Cost cost_sort_estimate(PlannerInfo *root, List *pathkeys, + int nPresortedKeys, double tuples); +extern void cost_append(AppendPath *path, PlannerInfo *root); extern void cost_merge_append(Path *path, PlannerInfo *root, List *pathkeys, int n_streams, Cost input_startup_cost, Cost input_total_cost, @@ -174,6 +176,7 @@ extern void cost_gather_merge(GatherMergePath *path, PlannerInfo *root, extern void cost_subplan(PlannerInfo *root, SubPlan *subplan, Plan *plan); extern void cost_qual_eval(QualCost *cost, List *quals, PlannerInfo *root); extern void cost_qual_eval_node(QualCost *cost, Node *qual, PlannerInfo *root); +extern void cost_qual_eval_node_index(QualCost *cost, Node *qual, PlannerInfo *root); extern void compute_semi_anti_join_factors(PlannerInfo *root, RelOptInfo *joinrel, RelOptInfo *outerrel, diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h index d2d46b15df5..377c5d15f21 100644 --- a/src/include/optimizer/pathnode.h +++ b/src/include/optimizer/pathnode.h @@ -71,7 +71,7 @@ extern AppendPath *create_append_path(PlannerInfo *root, RelOptInfo *rel, List *subpaths, List *partial_subpaths, List *pathkeys, Relids required_outer, int parallel_workers, bool parallel_aware, - double rows); + double rows, bool pull_tlist); extern MergeAppendPath *create_merge_append_path(PlannerInfo *root, RelOptInfo *rel, List *subpaths, @@ -309,6 +309,11 @@ extern RelOptInfo *build_join_rel(PlannerInfo *root, RelOptInfo *inner_rel, SpecialJoinInfo *sjinfo, List **restrictlist_ptr); + +extern List *build_joinrel_restrictlist(PlannerInfo *root, + Relids joinrelids, + RelOptInfo *outer_rel, + RelOptInfo *inner_rel); extern Relids min_join_parameterization(PlannerInfo *root, Relids joinrelids, RelOptInfo *outer_rel, diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h index e313eb21384..a6d1b59f56f 100644 --- a/src/include/optimizer/paths.h +++ b/src/include/optimizer/paths.h @@ -24,6 +24,7 @@ extern PGDLLIMPORT bool enable_geqo; extern PGDLLIMPORT int geqo_threshold; extern PGDLLIMPORT int min_parallel_table_scan_size; extern PGDLLIMPORT int min_parallel_index_scan_size; +extern PGDLLIMPORT bool enable_group_by_reordering; /* Hook for plugins to get control in set_rel_pathlist() */ typedef void (*set_rel_pathlist_hook_type) (PlannerInfo *root, @@ -72,9 +73,22 @@ extern void debug_print_rel(PlannerInfo *root, RelOptInfo *rel); * routines to generate index paths */ extern void create_index_paths(PlannerInfo *root, RelOptInfo *rel); +extern List *generate_bitmap_or_paths(PlannerInfo *root, RelOptInfo *rel, + List *clauses, List *other_clauses); +/* + * UniqueIndexInfo describes a unique index and its corresponding clauses + * that guarantee the uniqueness of a relation. + */ +typedef struct UniqueIndexInfo +{ + IndexOptInfo *index; + List *clauses; +} UniqueIndexInfo; + extern bool relation_has_unique_index_for(PlannerInfo *root, RelOptInfo *rel, List *restrictlist, - List *exprlist, List *oprlist); + List *exprlist, List *oprlist, + UniqueIndexInfo **info); extern bool indexcol_is_bool_constant_for_query(PlannerInfo *root, IndexOptInfo *index, int indexcol); @@ -202,6 +216,12 @@ typedef enum extern PathKeysComparison compare_pathkeys(List *keys1, List *keys2); extern bool pathkeys_contained_in(List *keys1, List *keys2); extern bool pathkeys_count_contained_in(List *keys1, List *keys2, int *n_common); +extern int group_keys_reorder_by_pathkeys(List *pathkeys, + List **group_pathkeys, + List **group_clauses); +extern List *get_useful_group_keys_orderings(PlannerInfo *root, double nrows, + List *path_pathkeys, + List *group_pathkeys, List *group_clauses); extern Path *get_cheapest_path_for_pathkeys(List *paths, List *pathkeys, Relids required_outer, CostSelector cost_criterion, @@ -241,6 +261,7 @@ extern List *select_outer_pathkeys_for_merge(PlannerInfo *root, extern List *make_inner_pathkeys_for_merge(PlannerInfo *root, List *mergeclauses, List *outer_pathkeys); +extern int pathkeys_useful_for_ordering(PlannerInfo *root, List *pathkeys); extern List *trim_mergeclauses_for_inner_pathkeys(PlannerInfo *root, List *mergeclauses, List *pathkeys); @@ -248,6 +269,7 @@ extern List *truncate_useless_pathkeys(PlannerInfo *root, RelOptInfo *rel, List *pathkeys); extern bool has_useful_pathkeys(PlannerInfo *root, RelOptInfo *rel); +extern void keybased_rewrite_index_paths(PlannerInfo *root, RelOptInfo *rel); extern PathKey *make_canonical_pathkey(PlannerInfo *root, EquivalenceClass *eclass, Oid opfamily, int strategy, bool nulls_first); diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h index c4f61c1a09c..fb6aa8276b8 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 e03d317eeac..9732690ca98 100644 --- a/src/include/storage/lwlock.h +++ b/src/include/storage/lwlock.h @@ -23,6 +23,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 2579e619ebc..2ea773098b1 100644 --- a/src/include/storage/proc.h +++ b/src/include/storage/proc.h @@ -211,7 +211,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 4d3ffc767f4..eac431a0624 100644 --- a/src/include/storage/s_lock.h +++ b/src/include/storage/s_lock.h @@ -458,6 +458,17 @@ do \ #endif /* __sparc__ */ +/* Elbrus */ +#ifdef __e2k__ +#define HAS_TEST_AND_SET +typedef int slock_t; +/* There is no need to check for sync_lock availability. */ +#define TAS(lock) __sync_lock_test_and_set(lock, 1) +#define S_UNLOCK(lock) __sync_lock_release(lock) +#define SPIN_DELAY() do { __asm__ __volatile__ ("nop" : : ); } while(0) +#endif + + /* PowerPC */ #if defined(__ppc__) || defined(__powerpc__) || defined(__ppc64__) || defined(__powerpc64__) #define HAS_TEST_AND_SET diff --git a/src/include/storage/sinval.h b/src/include/storage/sinval.h index e7cd45658c6..2dd66d8d559 100644 --- a/src/include/storage/sinval.h +++ b/src/include/storage/sinval.h @@ -60,6 +60,7 @@ typedef struct { int8 id; /* cache ID --- must be first */ + bool isLocal; Oid dbId; /* database ID, or 0 if a shared relation */ uint32 hashValue; /* hash value of key for this catcache */ } SharedInvalCatcacheMsg; @@ -78,6 +79,7 @@ typedef struct typedef struct { int8 id; /* type field --- must be first */ + bool isLocal; Oid dbId; /* database ID, or 0 if a shared relation */ Oid relId; /* relation ID, or 0 if whole relcache */ } SharedInvalRelcacheMsg; diff --git a/src/include/utils/catcache.h b/src/include/utils/catcache.h index d81e6fabb73..1ac4384b7c5 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 854c3312414..ea95dad7f54 100644 --- a/src/include/utils/hsearch.h +++ b/src/include/utils/hsearch.h @@ -122,6 +122,8 @@ typedef struct HTAB *hashp; uint32 curBucket; /* index of current bucket */ HASHELEMENT *curEntry; /* current entry in bucket */ + bool hasHashVal; + uint32 hashvalue; } HASH_SEQ_STATUS; /* @@ -141,6 +143,8 @@ extern bool hash_update_hash_key(HTAB *hashp, void *existingEntry, const void *newKeyPtr); extern long hash_get_num_entries(HTAB *hashp); extern void hash_seq_init(HASH_SEQ_STATUS *status, HTAB *hashp); +extern void hash_seq_init_with_hash_value(HASH_SEQ_STATUS *status, HTAB *hashp, + uint32 hashvalue); extern void *hash_seq_search(HASH_SEQ_STATUS *status); extern void hash_seq_term(HASH_SEQ_STATUS *status); extern void hash_freeze(HTAB *hashp); diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h index b8dd27d4a96..bd815c1231c 100644 --- a/src/include/utils/lsyscache.h +++ b/src/include/utils/lsyscache.h @@ -59,6 +59,8 @@ typedef struct AttStatsSlot /* Remaining fields are private to get_attstatsslot/free_attstatsslot */ void *values_arr; /* palloc'd values array, if any */ void *numbers_arr; /* palloc'd numbers array, if any */ + + bool incache; /* do not free because struct is cached */ } AttStatsSlot; /* Hook for plugins to get control in get_attavgwidth() */ @@ -187,6 +189,8 @@ extern int32 get_typavgwidth(Oid typid, int32 typmod); extern int32 get_attavgwidth(Oid relid, AttrNumber attnum); extern bool get_attstatsslot(AttStatsSlot *sslot, HeapTuple statstuple, int reqkind, Oid reqop, int flags); +extern AttStatsSlot* fill_attstatsslot(AttStatsSlot *sslots, HeapTuple statstuple, + int reqkind, Oid reqop, int flags); extern void free_attstatsslot(AttStatsSlot *sslot); extern char *get_namespace_name(Oid nspid); extern char *get_namespace_name_or_temp(Oid nspid); diff --git a/src/include/utils/selfuncs.h b/src/include/utils/selfuncs.h index 8f3d73edfb2..6858fe80ec3 100644 --- a/src/include/utils/selfuncs.h +++ b/src/include/utils/selfuncs.h @@ -96,6 +96,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) \ @@ -174,6 +175,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, @@ -188,7 +192,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, @@ -214,6 +217,11 @@ extern double estimate_num_groups(PlannerInfo *root, List *groupExprs, double input_rows, List **pgset, EstimationInfo *estinfo); +extern double estimate_num_groups_incremental(PlannerInfo *root, List *groupExprs, + double input_rows, List **pgset, + EstimationInfo *estinfo, + List **cache_varinfos, int prevNExprs); + extern void estimate_hash_bucket_stats(PlannerInfo *root, Node *hashkey, double nbuckets, Selectivity *mcv_freq, @@ -237,5 +245,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/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out index 26031bc7800..a58b9c55b93 100644 --- a/src/test/regress/expected/aggregates.out +++ b/src/test/regress/expected/aggregates.out @@ -1210,7 +1210,8 @@ explain (costs off) select distinct min(f1), max(f1) from minmaxtest; QUERY PLAN --------------------------------------------------------------------------------------------- - Unique + HashAggregate + Group Key: $0, $1 InitPlan 1 (returns $0) -> Limit -> Merge Append @@ -1233,10 +1234,8 @@ explain (costs off) -> Index Only Scan using minmaxtest2i on minmaxtest2 minmaxtest_8 Index Cond: (f1 IS NOT NULL) -> Index Only Scan Backward using minmaxtest3i on minmaxtest3 minmaxtest_9 - -> Sort - Sort Key: ($0), ($1) - -> Result -(26 rows) + -> Result +(25 rows) select distinct min(f1), max(f1) from minmaxtest; min | max @@ -2454,6 +2453,241 @@ SELECT balk(hundred) FROM tenk1; (1 row) ROLLBACK; +-- GROUP BY optimization by reorder columns +SELECT + i AS id, + i/2 AS p, + format('%60s', i%2) AS v, + i/4 AS c, + i/8 AS d, + (random() * (10000/8))::int as e --the same as d but no correlation with p + INTO btg +FROM + generate_series(1, 10000) i; +VACUUM btg; +ANALYZE btg; +-- GROUP BY optimization by reorder columns by frequency +SET enable_hashagg=off; +SET max_parallel_workers= 0; +SET max_parallel_workers_per_gather = 0; +EXPLAIN (COSTS off) +SELECT count(*) FROM btg GROUP BY p, v; + QUERY PLAN +----------------------------- + GroupAggregate + Group Key: p, v + -> Sort + Sort Key: p, v + -> Seq Scan on btg +(5 rows) + +EXPLAIN (COSTS off) +SELECT count(*) FROM btg GROUP BY v, p; + QUERY PLAN +----------------------------- + GroupAggregate + Group Key: p, v + -> Sort + Sort Key: p, v + -> Seq Scan on btg +(5 rows) + +EXPLAIN (COSTS off) +SELECT count(*) FROM btg GROUP BY v, p, c; + QUERY PLAN +----------------------------- + GroupAggregate + Group Key: p, c, v + -> Sort + Sort Key: p, c, v + -> Seq Scan on btg +(5 rows) + +EXPLAIN (COSTS off) +SELECT count(*) FROM btg GROUP BY v, p, c ORDER BY v, p, c; + QUERY PLAN +----------------------------- + GroupAggregate + Group Key: v, p, c + -> Sort + Sort Key: v, p, c + -> Seq Scan on btg +(5 rows) + +EXPLAIN (COSTS off) +SELECT count(*) FROM btg GROUP BY v, p, d, c; + QUERY PLAN +------------------------------ + GroupAggregate + Group Key: p, d, c, v + -> Sort + Sort Key: p, d, c, v + -> Seq Scan on btg +(5 rows) + +EXPLAIN (COSTS off) +SELECT count(*) FROM btg GROUP BY v, p, d, c ORDER BY v, p, d ,c; + QUERY PLAN +------------------------------ + GroupAggregate + Group Key: v, p, d, c + -> Sort + Sort Key: v, p, d, c + -> Seq Scan on btg +(5 rows) + +EXPLAIN (COSTS off) +SELECT count(*) FROM btg GROUP BY v, p, d, c ORDER BY p, v, d ,c; + QUERY PLAN +------------------------------ + GroupAggregate + Group Key: p, v, d, c + -> Sort + Sort Key: p, v, d, c + -> Seq Scan on btg +(5 rows) + +EXPLAIN (COSTS off) +SELECT count(*) FROM btg GROUP BY p, d, e; + QUERY PLAN +----------------------------- + GroupAggregate + Group Key: p, d, e + -> Sort + Sort Key: p, d, e + -> Seq Scan on btg +(5 rows) + +EXPLAIN (COSTS off) +SELECT count(*) FROM btg GROUP BY p, e, d; + QUERY PLAN +----------------------------- + GroupAggregate + Group Key: p, e, d + -> Sort + Sort Key: p, e, d + -> Seq Scan on btg +(5 rows) + +CREATE STATISTICS btg_dep ON d, e, p FROM btg; +ANALYZE btg; +EXPLAIN (COSTS off) +SELECT count(*) FROM btg GROUP BY p, d, e; + QUERY PLAN +----------------------------- + GroupAggregate + Group Key: p, d, e + -> Sort + Sort Key: p, d, e + -> Seq Scan on btg +(5 rows) + +EXPLAIN (COSTS off) +SELECT count(*) FROM btg GROUP BY p, e, d; + QUERY PLAN +----------------------------- + GroupAggregate + Group Key: p, e, d + -> Sort + Sort Key: p, e, d + -> Seq Scan on btg +(5 rows) + +-- GROUP BY optimization by reorder columns by index scan +CREATE INDEX ON btg(p, v); +SET enable_seqscan=off; +SET enable_bitmapscan=off; +VACUUM btg; +EXPLAIN (COSTS off) +SELECT count(*) FROM btg GROUP BY p, v; + QUERY PLAN +------------------------------------------------ + GroupAggregate + Group Key: p, v + -> Index Only Scan using btg_p_v_idx on btg +(3 rows) + +EXPLAIN (COSTS off) +SELECT count(*) FROM btg GROUP BY p, v ORDER BY p, v; + QUERY PLAN +------------------------------------------------ + GroupAggregate + Group Key: p, v + -> Index Only Scan using btg_p_v_idx on btg +(3 rows) + +EXPLAIN (COSTS off) +SELECT count(*) FROM btg GROUP BY v, p; + QUERY PLAN +------------------------------------------------ + GroupAggregate + Group Key: p, v + -> Index Only Scan using btg_p_v_idx on btg +(3 rows) + +EXPLAIN (COSTS off) +SELECT count(*) FROM btg GROUP BY v, p ORDER BY p, v; + QUERY PLAN +------------------------------------------------ + GroupAggregate + Group Key: p, v + -> Index Only Scan using btg_p_v_idx on btg +(3 rows) + +EXPLAIN (COSTS off) +SELECT count(*) FROM btg GROUP BY v, p, c; + QUERY PLAN +------------------------------------------------- + GroupAggregate + Group Key: p, c, v + -> Incremental Sort + Sort Key: p, c, v + Presorted Key: p + -> Index Scan using btg_p_v_idx on btg +(6 rows) + +EXPLAIN (COSTS off) +SELECT count(*) FROM btg GROUP BY v, p, c ORDER BY p, v; + QUERY PLAN +------------------------------------------------- + GroupAggregate + Group Key: p, v, c + -> Incremental Sort + Sort Key: p, v, c + Presorted Key: p, v + -> Index Scan using btg_p_v_idx on btg +(6 rows) + +EXPLAIN (COSTS off) +SELECT count(*) FROM btg GROUP BY v, c, p, d; + QUERY PLAN +------------------------------------------------- + GroupAggregate + Group Key: p, c, d, v + -> Incremental Sort + Sort Key: p, c, d, v + Presorted Key: p + -> Index Scan using btg_p_v_idx on btg +(6 rows) + +EXPLAIN (COSTS off) +SELECT count(*) FROM btg GROUP BY v, c, p, d ORDER BY p, v; + QUERY PLAN +------------------------------------------------- + GroupAggregate + Group Key: p, v, c, d + -> Incremental Sort + Sort Key: p, v, c, d + Presorted Key: p, v + -> Index Scan using btg_p_v_idx on btg +(6 rows) + +DROP TABLE btg; +RESET enable_hashagg; +RESET max_parallel_workers; +RESET max_parallel_workers_per_gather; +RESET enable_seqscan; +RESET enable_bitmapscan; -- Secondly test the case of a parallel aggregate combiner function -- returning NULL. For that use normal transition function, but a -- combiner function returning NULL. diff --git a/src/test/regress/expected/create_index.out b/src/test/regress/expected/create_index.out index d55aec3a1d0..2a91dfeb12e 100644 --- a/src/test/regress/expected/create_index.out +++ b/src/test/regress/expected/create_index.out @@ -1832,18 +1832,12 @@ DROP TABLE onek_with_null; EXPLAIN (COSTS OFF) SELECT * FROM tenk1 WHERE thousand = 42 AND (tenthous = 1 OR tenthous = 3 OR tenthous = 42); - QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------ - Bitmap Heap Scan on tenk1 - Recheck Cond: (((thousand = 42) AND (tenthous = 1)) OR ((thousand = 42) AND (tenthous = 3)) OR ((thousand = 42) AND (tenthous = 42))) - -> BitmapOr - -> Bitmap Index Scan on tenk1_thous_tenthous - Index Cond: ((thousand = 42) AND (tenthous = 1)) - -> Bitmap Index Scan on tenk1_thous_tenthous - Index Cond: ((thousand = 42) AND (tenthous = 3)) - -> Bitmap Index Scan on tenk1_thous_tenthous - Index Cond: ((thousand = 42) AND (tenthous = 42)) -(9 rows) + QUERY PLAN +----------------------------------------------------------------- + Index Scan using tenk1_thous_tenthous on tenk1 + Index Cond: ((thousand = 42) AND (thousand = 42)) + Filter: ((tenthous = 1) OR (tenthous = 3) OR (tenthous = 42)) +(3 rows) SELECT * FROM tenk1 WHERE thousand = 42 AND (tenthous = 1 OR tenthous = 3 OR tenthous = 42); diff --git a/src/test/regress/expected/equivclass.out b/src/test/regress/expected/equivclass.out index 126f7047fed..335929704f4 100644 --- a/src/test/regress/expected/equivclass.out +++ b/src/test/regress/expected/equivclass.out @@ -439,6 +439,38 @@ explain (costs off) Filter: ((unique1 = unique1) OR (unique2 = unique2)) (2 rows) +-- Test that broken ECs are processed correctly during self join removal. +-- Disable merge joins so that we don't get an error about missing commutator. +-- Test both orientations of the join clause, because only one of them breaks +-- the EC. +set enable_mergejoin to off; +explain (costs off) + select * from ec0 m join ec0 n on m.ff = n.ff + join ec1 p on m.ff + n.ff = p.f1; + QUERY PLAN +---------------------------------------- + Nested Loop + Join Filter: ((n.ff + n.ff) = p.f1) + -> Seq Scan on ec1 p + -> Materialize + -> Seq Scan on ec0 n + Filter: (ff IS NOT NULL) +(6 rows) + +explain (costs off) + select * from ec0 m join ec0 n on m.ff = n.ff + join ec1 p on p.f1::int8 = (m.ff + n.ff)::int8alias1; + QUERY PLAN +--------------------------------------------------------------- + Nested Loop + Join Filter: ((p.f1)::bigint = ((n.ff + n.ff))::int8alias1) + -> Seq Scan on ec1 p + -> Materialize + -> Seq Scan on ec0 n + Filter: (ff IS NOT NULL) +(6 rows) + +reset enable_mergejoin; -- check that we recognize equivalence with dummy domains in the way create temp table undername (f1 name, f2 int); create temp view overview as diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out index b1ea04181c6..b30774c7480 100644 --- a/src/test/regress/expected/guc.out +++ b/src/test/regress/expected/guc.out @@ -823,7 +823,7 @@ reset check_function_bodies; set default_with_oids to f; -- Should not allow to set it to true. set default_with_oids to t; -ERROR: tables declared WITH OIDS are not supported +WARNING: tables declared WITH OIDS are not supported, ignored -- Test GUC categories and flag patterns SELECT pg_settings_get_flags(NULL); pg_settings_get_flags diff --git a/src/test/regress/expected/incremental_sort.out b/src/test/regress/expected/incremental_sort.out index 0a631124c22..f35bd97bec6 100644 --- a/src/test/regress/expected/incremental_sort.out +++ b/src/test/regress/expected/incremental_sort.out @@ -1439,7 +1439,7 @@ set parallel_setup_cost = 0; set parallel_tuple_cost = 0; set max_parallel_workers_per_gather = 2; create table t (a int, b int, c int); -insert into t select mod(i,10),mod(i,10),i from generate_series(1,10000) s(i); +insert into t select mod(i,10),mod(i,10),i from generate_series(1,60000) s(i); create index on t (a); analyze t; set enable_incremental_sort = off; @@ -1460,21 +1460,19 @@ explain (costs off) select a,b,sum(c) from t group by 1,2 order by 1,2,3 limit 1 set enable_incremental_sort = on; explain (costs off) select a,b,sum(c) from t group by 1,2 order by 1,2,3 limit 1; - QUERY PLAN ----------------------------------------------------------------------- + QUERY PLAN +------------------------------------------------------ Limit - -> Incremental Sort + -> Sort Sort Key: a, b, (sum(c)) - Presorted Key: a, b - -> GroupAggregate + -> Finalize HashAggregate Group Key: a, b - -> Gather Merge + -> Gather Workers Planned: 2 - -> Incremental Sort - Sort Key: a, b - Presorted Key: a - -> Parallel Index Scan using t_a_idx on t -(12 rows) + -> Partial HashAggregate + Group Key: a, b + -> Parallel Seq Scan on t +(10 rows) -- Incremental sort vs. set operations with varno 0 set enable_hashagg to off; diff --git a/src/test/regress/expected/index_including.out b/src/test/regress/expected/index_including.out index 86510687c74..b41debaf2e8 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 0eb6339f608..7bc61b2bafd 100644 --- a/src/test/regress/expected/join.out +++ b/src/test/regress/expected/join.out @@ -1984,8 +1984,8 @@ USING (name); ------+----+---- bb | 12 | 13 cc | 22 | 23 - dd | | 33 ee | 42 | + dd | | 33 (4 rows) -- Cases with non-nullable expressions in subquery results; @@ -2019,8 +2019,8 @@ NATURAL FULL JOIN ------+------+------+------+------ bb | 12 | 2 | 13 | 3 cc | 22 | 2 | 23 | 3 - dd | | | 33 | 3 ee | 42 | 2 | | + dd | | | 33 | 3 (4 rows) SELECT * FROM @@ -4518,15 +4518,16 @@ explain (costs off) explain (costs off) select * from tenk1 a full join tenk1 b using(unique2) where unique2 = 42; - QUERY PLAN -------------------------------------------------- - Merge Full Join - Merge Cond: (a.unique2 = b.unique2) + QUERY PLAN +------------------------------------------------------- + Hash Full Join + Hash Cond: (a.unique2 = b.unique2) -> Index Scan using tenk1_unique2 on tenk1 a Index Cond: (unique2 = 42) - -> Index Scan using tenk1_unique2 on tenk1 b - Index Cond: (unique2 = 42) -(6 rows) + -> Hash + -> Index Scan using tenk1_unique2 on tenk1 b + Index Cond: (unique2 = 42) +(7 rows) -- -- test that quals attached to an outer join have correct semantics, @@ -4665,18 +4666,20 @@ select d.* from d left join (select * from b group by b.id, b.c_id) s explain (costs off) select d.* from d left join (select distinct * from b) s on d.a = s.id; - QUERY PLAN --------------------------------------- - Merge Right Join - Merge Cond: (b.id = d.a) - -> Unique - -> Sort - Sort Key: b.id, b.c_id - -> Seq Scan on b + QUERY PLAN +--------------------------------------------- + Merge Left Join + Merge Cond: (d.a = s.id) -> Sort Sort Key: d.a -> Seq Scan on d -(9 rows) + -> Sort + Sort Key: s.id + -> Subquery Scan on s + -> HashAggregate + Group Key: b.id, b.c_id + -> Seq Scan on b +(11 rows) -- check join removal works when uniqueness of the join condition is enforced -- by a UNION @@ -4969,6 +4972,226 @@ select * from ----+----+----+---- (0 rows) +-- +-- test that semi- or inner self-joins on a unique column are removed +-- +-- enable only nestloop to get more predictable plans +set enable_hashjoin to off; +set enable_mergejoin to off; +create table sj (a int unique, b int); +insert into sj values (1, null), (null, 2), (2, 1); +analyze sj; +select p.* from sj p, sj q where q.a = p.a and q.b = q.a - 1; + a | b +---+--- + 2 | 1 +(1 row) + +explain (costs off) +select p.* from sj p, sj q where q.a = p.a and q.b = q.a - 1; + QUERY PLAN +----------------------------------------------- + Seq Scan on sj q + Filter: ((a IS NOT NULL) AND (b = (a - 1))) +(2 rows) + +explain (costs off) +select * from sj p +where exists (select * from sj q + where q.a = p.a and q.b < 10); + QUERY PLAN +------------------------------------------ + Seq Scan on sj q + Filter: ((a IS NOT NULL) AND (b < 10)) +(2 rows) + +-- Double self-join removal. +-- Use a condition on "b + 1", not on "b", for the second join, so that +-- the equivalence class is different from the first one, and we can +-- test the non-ec code path. +explain (costs off) +select * from sj t1 join sj t2 on t1.a = t2.a and t1.b = t2.b + join sj t3 on t2.a = t3.a and t2.b + 1 = t3.b + 1; + QUERY PLAN +--------------------------------------------------------------------------- + Seq Scan on sj t3 + Filter: ((a IS NOT NULL) AND (b IS NOT NULL) AND ((b + 1) IS NOT NULL)) +(2 rows) + +-- subselect that references the removed relation +explain (costs off) +select t1.a, (select a from sj where a = t2.a and a = t1.a) +from sj t1, sj t2 +where t1.a = t2.a; + QUERY PLAN +------------------------------------------ + Seq Scan on sj t2 + Filter: (a IS NOT NULL) + SubPlan 1 + -> Result + One-Time Filter: (t2.a = t2.a) + -> Seq Scan on sj + Filter: (a = t2.a) +(7 rows) + +-- self-join under outer join +explain (costs off) +select * from sj x join sj y on x.a = y.a +left join int8_tbl z on x.a = z.q1; + QUERY PLAN +------------------------------------ + Nested Loop Left Join + Join Filter: (y.a = z.q1) + -> Seq Scan on sj y + Filter: (a IS NOT NULL) + -> Materialize + -> Seq Scan on int8_tbl z +(6 rows) + +explain (costs off) +select * from sj x join sj y on x.a = y.a +left join int8_tbl z on y.a = z.q1; + QUERY PLAN +------------------------------------ + Nested Loop Left Join + Join Filter: (y.a = z.q1) + -> Seq Scan on sj y + Filter: (a IS NOT NULL) + -> Materialize + -> Seq Scan on int8_tbl z +(6 rows) + +-- Test that placeholders are updated correctly after join removal +explain (costs off) +select * from (values (1)) x +left join (select coalesce(y.q1, 1) from int8_tbl y + right join sj j1 inner join sj j2 on j1.a = j2.a + on true) z +on true; + QUERY PLAN +------------------------------------------ + Nested Loop Left Join + -> Result + -> Nested Loop Left Join + -> Seq Scan on sj j2 + Filter: (a IS NOT NULL) + -> Materialize + -> Seq Scan on int8_tbl y +(7 rows) + +-- update lateral references and range table entry reference +explain (verbose, costs off) +select 1 from (select x.* from sj x, sj y where x.a = y.a) q, + lateral generate_series(1, q.a) gs(i); + QUERY PLAN +------------------------------------------------------ + Nested Loop + Output: 1 + -> Seq Scan on public.sj y + Output: y.a, y.b + Filter: (y.a IS NOT NULL) + -> Function Scan on pg_catalog.generate_series gs + Output: gs.i + Function Call: generate_series(1, y.a) +(8 rows) + +explain (verbose, costs off) +select 1 from (select y.* from sj x, sj y where x.a = y.a) q, + lateral generate_series(1, q.a) gs(i); + QUERY PLAN +------------------------------------------------------ + Nested Loop + Output: 1 + -> Seq Scan on public.sj y + Output: y.a, y.b + Filter: (y.a IS NOT NULL) + -> Function Scan on pg_catalog.generate_series gs + Output: gs.i + Function Call: generate_series(1, y.a) +(8 rows) + +-- Test that a non-EC-derived join clause is processed correctly. Use an +-- outer join so that we can't form an EC. +explain (costs off) select * from sj p join sj q on p.a = q.a + left join sj r on p.a + q.a = r.a; + QUERY PLAN +------------------------------------ + Nested Loop Left Join + Join Filter: ((q.a + q.a) = r.a) + -> Seq Scan on sj q + Filter: (a IS NOT NULL) + -> Materialize + -> Seq Scan on sj r +(6 rows) + +-- Check that attr_needed is updated correctly after self-join removal. In +-- this test, k1.b is required at either j1 or j2. If this info is lost, +-- join targetlist for (k1, k2) will not contain k1.b. Use index scan for +-- k1 so that we don't get 'b' from physical tlist used for seqscan. Also +-- disable reordering of joins because this test depends on a particular +-- join tree. +create table sk (a int, b int); +create index sk_a_idx on sk(a); +set join_collapse_limit to 1; +set enable_seqscan to off; +explain (costs off) select 1 from + (sk k1 join sk k2 on k1.a = k2.a) + join (sj j1 join sj j2 on j1.a = j2.a) on j1.b = k1.b; + QUERY PLAN +----------------------------------------------------- + Nested Loop + Join Filter: (k1.b = j2.b) + -> Nested Loop + -> Index Scan using sk_a_idx on sk k1 + -> Index Only Scan using sk_a_idx on sk k2 + Index Cond: (a = k1.a) + -> Materialize + -> Index Scan using sj_a_key on sj j2 + Index Cond: (a IS NOT NULL) +(9 rows) + +explain (costs off) select 1 from + (sk k1 join sk k2 on k1.a = k2.a) + join (sj j1 join sj j2 on j1.a = j2.a) on j2.b = k1.b; + QUERY PLAN +----------------------------------------------------- + Nested Loop + Join Filter: (k1.b = j2.b) + -> Nested Loop + -> Index Scan using sk_a_idx on sk k1 + -> Index Only Scan using sk_a_idx on sk k2 + Index Cond: (a = k1.a) + -> Materialize + -> Index Scan using sj_a_key on sj j2 + Index Cond: (a IS NOT NULL) +(9 rows) + +reset join_collapse_limit; +reset enable_seqscan; +-- If index conditions are different for each side, we won't select the same +-- row on both sides, so the join can't be removed. +create table sl(a int, b int); +create unique index sl_ab on sl(a, b); +explain (costs off) +select * from sl t1, sl t2 +where t1.a = t2.a and t1.b = 1 and t2.b = 2; + QUERY PLAN +---------------------------------------------- + Nested Loop + Join Filter: (t1.a = t2.a) + -> Bitmap Heap Scan on sl t1 + Recheck Cond: (b = 1) + -> Bitmap Index Scan on sl_ab + Index Cond: (b = 1) + -> Materialize + -> Bitmap Heap Scan on sl t2 + Recheck Cond: (b = 2) + -> Bitmap Index Scan on sl_ab + Index Cond: (b = 2) +(11 rows) + +reset enable_hashjoin; +reset enable_mergejoin; -- -- Test hints given on incorrect column references are useful -- @@ -5388,15 +5611,15 @@ select * from lateral (values(x.q1,y.q1,y.q2)) v(xq1,yq1,yq2); q1 | q2 | q1 | q2 | xq1 | yq1 | yq2 ------------------+-------------------+------------------+-------------------+------------------+------------------+------------------- - 123 | 456 | | | 123 | | - 123 | 4567890123456789 | 4567890123456789 | -4567890123456789 | 123 | 4567890123456789 | -4567890123456789 - 123 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 123 | 4567890123456789 | 4567890123456789 - 123 | 4567890123456789 | 4567890123456789 | 123 | 123 | 4567890123456789 | 123 - 4567890123456789 | 123 | 123 | 4567890123456789 | 4567890123456789 | 123 | 4567890123456789 4567890123456789 | 123 | 123 | 456 | 4567890123456789 | 123 | 456 - 4567890123456789 | 4567890123456789 | 4567890123456789 | -4567890123456789 | 4567890123456789 | 4567890123456789 | -4567890123456789 - 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 + 4567890123456789 | 123 | 123 | 4567890123456789 | 4567890123456789 | 123 | 4567890123456789 4567890123456789 | 4567890123456789 | 4567890123456789 | 123 | 4567890123456789 | 4567890123456789 | 123 + 123 | 4567890123456789 | 4567890123456789 | 123 | 123 | 4567890123456789 | 123 + 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 + 123 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 123 | 4567890123456789 | 4567890123456789 + 4567890123456789 | 4567890123456789 | 4567890123456789 | -4567890123456789 | 4567890123456789 | 4567890123456789 | -4567890123456789 + 123 | 4567890123456789 | 4567890123456789 | -4567890123456789 | 123 | 4567890123456789 | -4567890123456789 + 123 | 456 | | | 123 | | 4567890123456789 | -4567890123456789 | | | 4567890123456789 | | (10 rows) @@ -5405,15 +5628,15 @@ select * from lateral (select x.q1,y.q1,y.q2) v(xq1,yq1,yq2); q1 | q2 | q1 | q2 | xq1 | yq1 | yq2 ------------------+-------------------+------------------+-------------------+------------------+------------------+------------------- - 123 | 456 | | | 123 | | - 123 | 4567890123456789 | 4567890123456789 | -4567890123456789 | 123 | 4567890123456789 | -4567890123456789 - 123 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 123 | 4567890123456789 | 4567890123456789 - 123 | 4567890123456789 | 4567890123456789 | 123 | 123 | 4567890123456789 | 123 - 4567890123456789 | 123 | 123 | 4567890123456789 | 4567890123456789 | 123 | 4567890123456789 4567890123456789 | 123 | 123 | 456 | 4567890123456789 | 123 | 456 - 4567890123456789 | 4567890123456789 | 4567890123456789 | -4567890123456789 | 4567890123456789 | 4567890123456789 | -4567890123456789 - 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 + 4567890123456789 | 123 | 123 | 4567890123456789 | 4567890123456789 | 123 | 4567890123456789 4567890123456789 | 4567890123456789 | 4567890123456789 | 123 | 4567890123456789 | 4567890123456789 | 123 + 123 | 4567890123456789 | 4567890123456789 | 123 | 123 | 4567890123456789 | 123 + 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 + 123 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 123 | 4567890123456789 | 4567890123456789 + 4567890123456789 | 4567890123456789 | 4567890123456789 | -4567890123456789 | 4567890123456789 | 4567890123456789 | -4567890123456789 + 123 | 4567890123456789 | 4567890123456789 | -4567890123456789 | 123 | 4567890123456789 | -4567890123456789 + 123 | 456 | | | 123 | | 4567890123456789 | -4567890123456789 | | | 4567890123456789 | | (10 rows) @@ -5440,24 +5663,24 @@ select v.* from lateral (select x.q1,y.q1 union all select x.q2,y.q2) v(vx,vy); vx | vy -------------------+------------------- - 123 | - 456 | - 123 | 4567890123456789 - 4567890123456789 | -4567890123456789 + 4567890123456789 | 123 + 123 | 456 + 4567890123456789 | 123 123 | 4567890123456789 4567890123456789 | 4567890123456789 - 123 | 4567890123456789 - 4567890123456789 | 123 4567890123456789 | 123 123 | 4567890123456789 4567890123456789 | 123 - 123 | 456 4567890123456789 | 4567890123456789 - 4567890123456789 | -4567890123456789 4567890123456789 | 4567890123456789 + 123 | 4567890123456789 4567890123456789 | 4567890123456789 4567890123456789 | 4567890123456789 - 4567890123456789 | 123 + 4567890123456789 | -4567890123456789 + 123 | 4567890123456789 + 4567890123456789 | -4567890123456789 + 123 | + 456 | 4567890123456789 | -4567890123456789 | (20 rows) @@ -5784,15 +6007,15 @@ select * from Hash Cond: (d.q1 = c.q2) -> Nested Loop Output: a.q1, a.q2, b.q1, d.q1, (COALESCE(b.q2, '42'::bigint)), (COALESCE((COALESCE(b.q2, '42'::bigint)), d.q2)) - -> Hash Left Join + -> Hash Right Join Output: a.q1, a.q2, b.q1, (COALESCE(b.q2, '42'::bigint)) - Hash Cond: (a.q2 = b.q1) - -> Seq Scan on public.int8_tbl a - Output: a.q1, a.q2 + Hash Cond: (b.q1 = a.q2) + -> Seq Scan on public.int8_tbl b + Output: b.q1, COALESCE(b.q2, '42'::bigint) -> Hash - Output: b.q1, (COALESCE(b.q2, '42'::bigint)) - -> Seq Scan on public.int8_tbl b - Output: b.q1, COALESCE(b.q2, '42'::bigint) + Output: a.q1, a.q2 + -> Seq Scan on public.int8_tbl a + Output: a.q1, a.q2 -> Seq Scan on public.int8_tbl d Output: d.q1, COALESCE((COALESCE(b.q2, '42'::bigint)), d.q2) -> Hash @@ -6386,44 +6609,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; @@ -6586,10 +6804,9 @@ where j1.id1 % 1000 = 1 and j2.id1 % 1000 = 1 and j2.id1 >= any (array[1,5]); Merge Cond: (j1.id1 = j2.id1) Join Filter: (j1.id2 = j2.id2) -> Index Scan using j1_id1_idx on j1 - -> Index Only Scan using j2_pkey on j2 + -> Index Scan using j2_id1_idx on j2 Index Cond: (id1 >= ANY ('{1,5}'::integer[])) - Filter: ((id1 % 1000) = 1) -(7 rows) +(6 rows) select * from j1 inner join j2 on j1.id1 = j2.id1 and j1.id2 = j2.id2 diff --git a/src/test/regress/expected/merge.out b/src/test/regress/expected/merge.out index 15c6632182c..3014dba2170 100644 --- a/src/test/regress/expected/merge.out +++ b/src/test/regress/expected/merge.out @@ -1459,18 +1459,15 @@ WHEN MATCHED AND t.a < 10 THEN explain_merge -------------------------------------------------------------------- Merge on ex_mtarget t (actual rows=0 loops=1) - -> Merge Join (actual rows=0 loops=1) - Merge Cond: (t.a = s.a) - -> Sort (actual rows=0 loops=1) - Sort Key: t.a - Sort Method: quicksort Memory: xxx + -> Hash Join (actual rows=0 loops=1) + Hash Cond: (s.a = t.a) + -> Seq Scan on ex_msource s (actual rows=1 loops=1) + -> Hash (actual rows=0 loops=1) + Buckets: xxx Batches: xxx Memory Usage: xxx -> Seq Scan on ex_mtarget t (actual rows=0 loops=1) Filter: (a < '-1000'::integer) Rows Removed by Filter: 54 - -> Sort (never executed) - Sort Key: s.a - -> Seq Scan on ex_msource s (never executed) -(12 rows) +(9 rows) DROP TABLE ex_msource, ex_mtarget; DROP FUNCTION explain_merge(text); diff --git a/src/test/regress/expected/partition_aggregate.out b/src/test/regress/expected/partition_aggregate.out index 0d69619a2f6..71f1debc4ca 100644 --- a/src/test/regress/expected/partition_aggregate.out +++ b/src/test/regress/expected/partition_aggregate.out @@ -729,10 +729,10 @@ EXPLAIN (COSTS OFF) SELECT a.x, b.y, count(*) FROM (SELECT * FROM pagg_tab1 WHERE x < 20) a LEFT JOIN (SELECT * FROM pagg_tab2 WHERE y > 10) b ON a.x = b.y WHERE a.x > 5 or b.y < 20 GROUP BY a.x, b.y ORDER BY 1, 2; QUERY PLAN -------------------------------------------------------------------- - Sort - Sort Key: pagg_tab1.x, pagg_tab2.y - -> HashAggregate - Group Key: pagg_tab1.x, pagg_tab2.y + GroupAggregate + Group Key: pagg_tab1.x, pagg_tab2.y + -> Sort + Sort Key: pagg_tab1.x, pagg_tab2.y -> Hash Left Join Hash Cond: (pagg_tab1.x = pagg_tab2.y) Filter: ((pagg_tab1.x > 5) OR (pagg_tab2.y < 20)) @@ -949,12 +949,12 @@ SET parallel_setup_cost = 0; -- is not partial agg safe. EXPLAIN (COSTS OFF) SELECT a, sum(b), array_agg(distinct c), count(*) FROM pagg_tab_ml GROUP BY a HAVING avg(b) < 3 ORDER BY 1, 2, 3; - QUERY PLAN --------------------------------------------------------------------------------------- - Sort - Sort Key: pagg_tab_ml.a, (sum(pagg_tab_ml.b)), (array_agg(DISTINCT pagg_tab_ml.c)) - -> Gather - Workers Planned: 2 + QUERY PLAN +-------------------------------------------------------------------------------------------- + Gather Merge + Workers Planned: 2 + -> Sort + Sort Key: pagg_tab_ml.a, (sum(pagg_tab_ml.b)), (array_agg(DISTINCT pagg_tab_ml.c)) -> Parallel Append -> GroupAggregate Group Key: pagg_tab_ml.a @@ -1381,28 +1381,26 @@ SELECT x, sum(y), avg(y), count(*) FROM pagg_tab_para GROUP BY x HAVING avg(y) < -- When GROUP BY clause does not match; partial aggregation is performed for each partition. EXPLAIN (COSTS OFF) SELECT y, sum(x), avg(x), count(*) FROM pagg_tab_para GROUP BY y HAVING avg(x) < 12 ORDER BY 1, 2, 3; - QUERY PLAN -------------------------------------------------------------------------------------------- + QUERY PLAN +------------------------------------------------------------------------------------- Sort Sort Key: pagg_tab_para.y, (sum(pagg_tab_para.x)), (avg(pagg_tab_para.x)) - -> Finalize GroupAggregate + -> Finalize HashAggregate Group Key: pagg_tab_para.y Filter: (avg(pagg_tab_para.x) < '12'::numeric) - -> Gather Merge + -> Gather Workers Planned: 2 - -> Sort - Sort Key: pagg_tab_para.y - -> Parallel Append - -> Partial HashAggregate - Group Key: pagg_tab_para.y - -> Parallel Seq Scan on pagg_tab_para_p1 pagg_tab_para - -> Partial HashAggregate - Group Key: pagg_tab_para_1.y - -> Parallel Seq Scan on pagg_tab_para_p2 pagg_tab_para_1 - -> Partial HashAggregate - Group Key: pagg_tab_para_2.y - -> Parallel Seq Scan on pagg_tab_para_p3 pagg_tab_para_2 -(19 rows) + -> Parallel Append + -> Partial HashAggregate + Group Key: pagg_tab_para.y + -> Parallel Seq Scan on pagg_tab_para_p1 pagg_tab_para + -> Partial HashAggregate + Group Key: pagg_tab_para_1.y + -> Parallel Seq Scan on pagg_tab_para_p2 pagg_tab_para_1 + -> Partial HashAggregate + Group Key: pagg_tab_para_2.y + -> Parallel Seq Scan on pagg_tab_para_p3 pagg_tab_para_2 +(17 rows) SELECT y, sum(x), avg(x), count(*) FROM pagg_tab_para GROUP BY y HAVING avg(x) < 12 ORDER BY 1, 2, 3; y | sum | avg | count diff --git a/src/test/regress/expected/partition_join.out b/src/test/regress/expected/partition_join.out index bb5b7c47a45..940f843c669 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 @@ -1996,29 +1980,24 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt4_n t2, prt2 t3 WHERE t1.a = t2.a QUERY PLAN -------------------------------------------------------- Hash Join - Hash Cond: (t2.a = t1.a) + Hash Cond: (t1.a = t2.a) -> Append - -> Seq Scan on prt4_n_p1 t2_1 - -> Seq Scan on prt4_n_p2 t2_2 - -> Seq Scan on prt4_n_p3 t2_3 + -> Seq Scan on prt1_p1 t1_1 + -> Seq Scan on prt1_p2 t1_2 + -> Seq Scan on prt1_p3 t1_3 -> Hash - -> Append - -> Hash Join - Hash Cond: (t1_1.a = t3_1.b) - -> Seq Scan on prt1_p1 t1_1 - -> Hash + -> Hash Join + Hash Cond: (t2.a = t3.b) + -> Append + -> Seq Scan on prt4_n_p1 t2_1 + -> Seq Scan on prt4_n_p2 t2_2 + -> Seq Scan on prt4_n_p3 t2_3 + -> Hash + -> Append -> Seq Scan on prt2_p1 t3_1 - -> Hash Join - Hash Cond: (t1_2.a = t3_2.b) - -> Seq Scan on prt1_p2 t1_2 - -> Hash -> Seq Scan on prt2_p2 t3_2 - -> Hash Join - Hash Cond: (t1_3.a = t3_3.b) - -> Seq Scan on prt1_p3 t1_3 - -> Hash -> Seq Scan on prt2_p3 t3_3 -(23 rows) +(18 rows) -- partitionwise join can not be applied if there are no equi-join conditions -- between partition keys diff --git a/src/test/regress/expected/select.out b/src/test/regress/expected/select.out index 33a6dceb0e3..e39d4594787 100644 --- a/src/test/regress/expected/select.out +++ b/src/test/regress/expected/select.out @@ -521,6 +521,124 @@ SELECT * FROM nocols n, LATERAL (VALUES(n.*)) v; -- (1 row) +-- +-- test order by NULLS (FIRST|LAST) +-- +select unique1, unique2 into onek_with_null from onek; +insert into onek_with_null (unique1,unique2) values (NULL, -1), (NULL, NULL); +select * from onek_with_null order by unique1 nulls first , unique2 limit 3; + unique1 | unique2 +---------+--------- + | -1 + | + 0 | 998 +(3 rows) + +select * from onek_with_null order by unique1 nulls last , unique2 limit 3; + unique1 | unique2 +---------+--------- + 0 | 998 + 1 | 214 + 2 | 326 +(3 rows) + +select * from onek_with_null order by unique1 nulls first , unique2 nulls first limit 3; + unique1 | unique2 +---------+--------- + | + | -1 + 0 | 998 +(3 rows) + +select * from onek_with_null order by unique1 nulls last , unique2 nulls first limit 3; + unique1 | unique2 +---------+--------- + 0 | 998 + 1 | 214 + 2 | 326 +(3 rows) + +select * from onek_with_null order by unique1 nulls first , unique2 nulls last limit 3; + unique1 | unique2 +---------+--------- + | -1 + | + 0 | 998 +(3 rows) + +select * from onek_with_null order by unique1 nulls last , unique2 nulls last limit 3; + unique1 | unique2 +---------+--------- + 0 | 998 + 1 | 214 + 2 | 326 +(3 rows) + +select * from onek_with_null order by unique1 desc nulls first , unique2 desc limit 3; + unique1 | unique2 +---------+--------- + | + | -1 + 999 | 152 +(3 rows) + +select * from onek_with_null order by unique1 desc nulls last , unique2 desc limit 3; + unique1 | unique2 +---------+--------- + 999 | 152 + 998 | 549 + 997 | 21 +(3 rows) + +select * from onek_with_null order by unique1 desc nulls first , unique2 desc nulls first limit 3; + unique1 | unique2 +---------+--------- + | + | -1 + 999 | 152 +(3 rows) + +select * from onek_with_null order by unique1 desc nulls last , unique2 desc nulls first limit 3; + unique1 | unique2 +---------+--------- + 999 | 152 + 998 | 549 + 997 | 21 +(3 rows) + +select * from onek_with_null order by unique1 desc nulls first , unique2 desc nulls last limit 3; + unique1 | unique2 +---------+--------- + | -1 + | + 999 | 152 +(3 rows) + +select * from onek_with_null order by unique1 desc nulls last , unique2 desc nulls last limit 3; + unique1 | unique2 +---------+--------- + 999 | 152 + 998 | 549 + 997 | 21 +(3 rows) + +select unique1 as u1, unique2 as u2 from onek_with_null order by u1 nulls first , u2 nulls first limit 3; + u1 | u2 +----+----- + | + | -1 + 0 | 998 +(3 rows) + +select unique1 as u1, unique2 as u2 from onek_with_null order by u1 asc nulls first , u2 desc nulls first limit 3; + u1 | u2 +----+----- + | + | -1 + 0 | 998 +(3 rows) + +drop table onek_with_null; -- -- Test ORDER BY options -- diff --git a/src/test/regress/expected/stats_ext.out b/src/test/regress/expected/stats_ext.out index 67cae4a83db..113390b4fa4 100644 --- a/src/test/regress/expected/stats_ext.out +++ b/src/test/regress/expected/stats_ext.out @@ -4,6 +4,7 @@ -- with autovacuum_enabled = off, so that we don't have unstable results -- from auto-analyze happening when we didn't expect it. -- +set default_statistics_target=10000; --prevent random subset for joinsel -- check the number of estimated/actual rows in the top node create function check_estimated_rows(text) returns table (estimated int, actual int) language plpgsql as @@ -1113,43 +1114,43 @@ SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 51) AND b = ''1'''); estimated | actual -----------+-------- - 2 | 100 + 99 | 100 (1 row) SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 51) AND b IN (''1'', ''2'')'); estimated | actual -----------+-------- - 4 | 100 + 100 | 100 (1 row) SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 2, 51, 52) AND b IN (''1'', ''2'')'); estimated | actual -----------+-------- - 8 | 200 + 197 | 200 (1 row) SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 2, 51, 52) AND b = ''1'''); estimated | actual -----------+-------- - 4 | 100 + 100 | 100 (1 row) SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 26, 51, 76) AND b IN (''1'', ''26'') AND c = 1'); estimated | actual -----------+-------- - 1 | 200 + 197 | 200 (1 row) SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 26, 51, 76) AND b IN (''1'', ''26'') AND c IN (1)'); estimated | actual -----------+-------- - 1 | 200 + 197 | 200 (1 row) SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 2, 26, 27, 51, 52, 76, 77) AND b IN (''1'', ''2'', ''26'', ''27'') AND c IN (1, 2)'); estimated | actual -----------+-------- - 3 | 400 + 386 | 400 (1 row) -- OR clauses referencing the same attribute @@ -1182,37 +1183,37 @@ SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = ANY (ARRAY[1, 51]) AND b = ''1'''); estimated | actual -----------+-------- - 2 | 100 + 99 | 100 (1 row) SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = ANY (ARRAY[1, 51]) AND b = ANY (ARRAY[''1'', ''2''])'); estimated | actual -----------+-------- - 4 | 100 + 100 | 100 (1 row) SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = ANY (ARRAY[1, 2, 51, 52]) AND b = ANY (ARRAY[''1'', ''2''])'); estimated | actual -----------+-------- - 8 | 200 + 197 | 200 (1 row) SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = ANY (ARRAY[1, 26, 51, 76]) AND b = ANY (ARRAY[''1'', ''26'']) AND c = 1'); estimated | actual -----------+-------- - 1 | 200 + 197 | 200 (1 row) SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = ANY (ARRAY[1, 26, 51, 76]) AND b = ANY (ARRAY[''1'', ''26'']) AND c = ANY (ARRAY[1])'); estimated | actual -----------+-------- - 1 | 200 + 197 | 200 (1 row) SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = ANY (ARRAY[1, 2, 26, 27, 51, 52, 76, 77]) AND b = ANY (ARRAY[''1'', ''2'', ''26'', ''27'']) AND c = ANY (ARRAY[1, 2])'); estimated | actual -----------+-------- - 3 | 400 + 386 | 400 (1 row) -- ANY with inequalities should not benefit from functional dependencies @@ -1878,7 +1879,7 @@ ANALYZE mcv_lists; SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = 1 AND b = ''1'''); estimated | actual -----------+-------- - 3 | 4 + 4 | 4 (1 row) SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = 1 AND b = ''1'' AND c = 1'); @@ -2815,7 +2816,7 @@ SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_partial WHERE a = 0 SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_partial WHERE a = 0 OR b = 0 OR c = 0'); estimated | actual -----------+-------- - 96 | 102 + 102 | 102 (1 row) SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_partial WHERE a = 10 AND b = 10 AND c = 10'); @@ -2839,7 +2840,7 @@ SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_partial WHERE a = 0 SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_partial WHERE a = 0 OR b = 0 OR c = 10'); estimated | actual -----------+-------- - 102 | 104 + 104 | 104 (1 row) SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_partial WHERE (a = 0 AND b = 0 AND c = 0) OR (a = 1 AND b = 1 AND c = 1) OR (a = 2 AND b = 2 AND c = 2)'); @@ -2851,7 +2852,7 @@ SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_partial WHERE (a = 0 SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_partial WHERE (a = 0 AND b = 0) OR (a = 0 AND c = 0) OR (b = 0 AND c = 0)'); estimated | actual -----------+-------- - 108 | 102 + 102 | 102 (1 row) DROP TABLE mcv_lists_partial; diff --git a/src/test/regress/expected/sysviews.out b/src/test/regress/expected/sysviews.out index 579b861d84f..4e775af1758 100644 --- a/src/test/regress/expected/sysviews.out +++ b/src/test/regress/expected/sysviews.out @@ -114,6 +114,7 @@ select name, setting from pg_settings where name like 'enable%'; enable_async_append | on enable_bitmapscan | on enable_gathermerge | on + enable_group_by_reordering | on enable_hashagg | on enable_hashjoin | on enable_incremental_sort | on @@ -131,7 +132,7 @@ select name, setting from pg_settings where name like 'enable%'; enable_seqscan | on enable_sort | on enable_tidscan | on -(20 rows) +(21 rows) -- Test that the pg_timezone_names and pg_timezone_abbrevs views are -- more-or-less working. We can't test their contents in any great detail diff --git a/src/test/regress/expected/type_sanity.out b/src/test/regress/expected/type_sanity.out index d3ac08c9ee3..5404e59bc45 100644 --- a/src/test/regress/expected/type_sanity.out +++ b/src/test/regress/expected/type_sanity.out @@ -171,10 +171,12 @@ WHERE t1.typinput = p1.oid AND t1.typtype in ('b', 'p') AND NOT (t1.typelem != 0 AND t1.typlen < 0) AND NOT (p1.prorettype = t1.oid AND NOT p1.proretset) ORDER BY 1; - oid | typname | oid | proname -------+-----------+-----+--------- - 1790 | refcursor | 46 | textin -(1 row) + oid | typname | oid | proname +-------+-----------+------+-------------- + 1790 | refcursor | 46 | textin + 14756 | abstime | 1312 | timestamp_in + 14757 | reltime | 1312 | timestamp_in +(3 rows) -- Varlena array types will point to array_in -- Exception as of 8.1: int2vector and oidvector have their own I/O routines @@ -223,10 +225,12 @@ WHERE t1.typoutput = p1.oid AND t1.typtype in ('b', 'p') AND NOT (p1.oid = 'array_out'::regproc AND t1.typelem != 0 AND t1.typlen = -1))) ORDER BY 1; - oid | typname | oid | proname -------+-----------+-----+--------- - 1790 | refcursor | 47 | textout -(1 row) + oid | typname | oid | proname +-------+-----------+------+--------------- + 1790 | refcursor | 47 | textout + 14756 | abstime | 1313 | timestamp_out + 14757 | reltime | 1313 | timestamp_out +(3 rows) SELECT t1.oid, t1.typname, p1.oid, p1.proname FROM pg_type AS t1, pg_proc AS p1 @@ -287,10 +291,12 @@ WHERE t1.typreceive = p1.oid AND t1.typtype in ('b', 'p') AND NOT (t1.typelem != 0 AND t1.typlen < 0) AND NOT (p1.prorettype = t1.oid AND NOT p1.proretset) ORDER BY 1; - oid | typname | oid | proname -------+-----------+------+---------- - 1790 | refcursor | 2414 | textrecv -(1 row) + oid | typname | oid | proname +-------+-----------+------+---------------- + 1790 | refcursor | 2414 | textrecv + 14756 | abstime | 2474 | timestamp_recv + 14757 | reltime | 2474 | timestamp_recv +(3 rows) -- Varlena array types will point to array_recv -- Exception as of 8.1: int2vector and oidvector have their own I/O routines @@ -348,10 +354,12 @@ WHERE t1.typsend = p1.oid AND t1.typtype in ('b', 'p') AND NOT (p1.oid = 'array_send'::regproc AND t1.typelem != 0 AND t1.typlen = -1))) ORDER BY 1; - oid | typname | oid | proname -------+-----------+------+---------- - 1790 | refcursor | 2415 | textsend -(1 row) + oid | typname | oid | proname +-------+-----------+------+---------------- + 1790 | refcursor | 2415 | textsend + 14756 | abstime | 2475 | timestamp_send + 14757 | reltime | 2475 | timestamp_send +(3 rows) SELECT t1.oid, t1.typname, p1.oid, p1.proname FROM pg_type AS t1, pg_proc AS p1 diff --git a/src/test/regress/expected/union.out b/src/test/regress/expected/union.out index dece7310cfe..7ac4a9380e2 100644 --- a/src/test/regress/expected/union.out +++ b/src/test/regress/expected/union.out @@ -1303,24 +1303,22 @@ select distinct q1 from union all select distinct * from int8_tbl i82) ss where q2 = q2; - QUERY PLAN ----------------------------------------------------------- - Unique - -> Merge Append - Sort Key: "*SELECT* 1".q1 + QUERY PLAN +---------------------------------------------------- + HashAggregate + Group Key: "*SELECT* 1".q1 + -> Append -> Subquery Scan on "*SELECT* 1" - -> Unique - -> Sort - Sort Key: i81.q1, i81.q2 - -> Seq Scan on int8_tbl i81 - Filter: (q2 IS NOT NULL) + -> HashAggregate + Group Key: i81.q1, i81.q2 + -> Seq Scan on int8_tbl i81 + Filter: (q2 IS NOT NULL) -> Subquery Scan on "*SELECT* 2" - -> Unique - -> Sort - Sort Key: i82.q1, i82.q2 - -> Seq Scan on int8_tbl i82 - Filter: (q2 IS NOT NULL) -(15 rows) + -> HashAggregate + Group Key: i82.q1, i82.q2 + -> Seq Scan on int8_tbl i82 + Filter: (q2 IS NOT NULL) +(13 rows) select distinct q1 from (select distinct * from int8_tbl i81 @@ -1339,24 +1337,22 @@ select distinct q1 from union all select distinct * from int8_tbl i82) ss where -q1 = q2; - QUERY PLAN --------------------------------------------------------- - Unique - -> Merge Append - Sort Key: "*SELECT* 1".q1 + QUERY PLAN +-------------------------------------------------- + HashAggregate + Group Key: "*SELECT* 1".q1 + -> Append -> Subquery Scan on "*SELECT* 1" - -> Unique - -> Sort - Sort Key: i81.q1, i81.q2 - -> Seq Scan on int8_tbl i81 - Filter: ((- q1) = q2) + -> HashAggregate + Group Key: i81.q1, i81.q2 + -> Seq Scan on int8_tbl i81 + Filter: ((- q1) = q2) -> Subquery Scan on "*SELECT* 2" - -> Unique - -> Sort - Sort Key: i82.q1, i82.q2 - -> Seq Scan on int8_tbl i82 - Filter: ((- q1) = q2) -(15 rows) + -> HashAggregate + Group Key: i82.q1, i82.q2 + -> Seq Scan on int8_tbl i82 + Filter: ((- q1) = q2) +(13 rows) select distinct q1 from (select distinct * from int8_tbl i81 diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out index c27e2e803dd..cb90ede895e 100644 --- a/src/test/regress/expected/updatable_views.out +++ b/src/test/regress/expected/updatable_views.out @@ -2499,16 +2499,13 @@ SELECT * FROM rw_view1; (1 row) EXPLAIN (costs off) DELETE FROM rw_view1 WHERE id = 1 AND snoop(data); - QUERY PLAN -------------------------------------------------------------------- + QUERY PLAN +-------------------------------------------------- Update on base_tbl base_tbl_1 - -> Nested Loop - -> Index Scan using base_tbl_pkey on base_tbl base_tbl_1 - Index Cond: (id = 1) - -> Index Scan using base_tbl_pkey on base_tbl - Index Cond: (id = 1) - Filter: ((NOT deleted) AND snoop(data)) -(7 rows) + -> Index Scan using base_tbl_pkey on base_tbl + Index Cond: (id = 1) + Filter: ((NOT deleted) AND snoop(data)) +(4 rows) DELETE FROM rw_view1 WHERE id = 1 AND snoop(data); NOTICE: snooped value: Row 1 diff --git a/src/test/regress/sql/aggregates.sql b/src/test/regress/sql/aggregates.sql index 48bea8af5f8..f04b2b56c2d 100644 --- a/src/test/regress/sql/aggregates.sql +++ b/src/test/regress/sql/aggregates.sql @@ -1028,6 +1028,105 @@ SELECT balk(hundred) FROM tenk1; ROLLBACK; +-- GROUP BY optimization by reorder columns + +SELECT + i AS id, + i/2 AS p, + format('%60s', i%2) AS v, + i/4 AS c, + i/8 AS d, + (random() * (10000/8))::int as e --the same as d but no correlation with p + INTO btg +FROM + generate_series(1, 10000) i; + +VACUUM btg; +ANALYZE btg; + +-- GROUP BY optimization by reorder columns by frequency + +SET enable_hashagg=off; +SET max_parallel_workers= 0; +SET max_parallel_workers_per_gather = 0; + +EXPLAIN (COSTS off) +SELECT count(*) FROM btg GROUP BY p, v; + +EXPLAIN (COSTS off) +SELECT count(*) FROM btg GROUP BY v, p; + +EXPLAIN (COSTS off) +SELECT count(*) FROM btg GROUP BY v, p, c; + +EXPLAIN (COSTS off) +SELECT count(*) FROM btg GROUP BY v, p, c ORDER BY v, p, c; + +EXPLAIN (COSTS off) +SELECT count(*) FROM btg GROUP BY v, p, d, c; + +EXPLAIN (COSTS off) +SELECT count(*) FROM btg GROUP BY v, p, d, c ORDER BY v, p, d ,c; + +EXPLAIN (COSTS off) +SELECT count(*) FROM btg GROUP BY v, p, d, c ORDER BY p, v, d ,c; + +EXPLAIN (COSTS off) +SELECT count(*) FROM btg GROUP BY p, d, e; + +EXPLAIN (COSTS off) +SELECT count(*) FROM btg GROUP BY p, e, d; + +CREATE STATISTICS btg_dep ON d, e, p FROM btg; +ANALYZE btg; + +EXPLAIN (COSTS off) +SELECT count(*) FROM btg GROUP BY p, d, e; + +EXPLAIN (COSTS off) +SELECT count(*) FROM btg GROUP BY p, e, d; + + +-- GROUP BY optimization by reorder columns by index scan + +CREATE INDEX ON btg(p, v); +SET enable_seqscan=off; +SET enable_bitmapscan=off; +VACUUM btg; + +EXPLAIN (COSTS off) +SELECT count(*) FROM btg GROUP BY p, v; + +EXPLAIN (COSTS off) +SELECT count(*) FROM btg GROUP BY p, v ORDER BY p, v; + +EXPLAIN (COSTS off) +SELECT count(*) FROM btg GROUP BY v, p; + +EXPLAIN (COSTS off) +SELECT count(*) FROM btg GROUP BY v, p ORDER BY p, v; + +EXPLAIN (COSTS off) +SELECT count(*) FROM btg GROUP BY v, p, c; + +EXPLAIN (COSTS off) +SELECT count(*) FROM btg GROUP BY v, p, c ORDER BY p, v; + +EXPLAIN (COSTS off) +SELECT count(*) FROM btg GROUP BY v, c, p, d; + +EXPLAIN (COSTS off) +SELECT count(*) FROM btg GROUP BY v, c, p, d ORDER BY p, v; + +DROP TABLE btg; + +RESET enable_hashagg; +RESET max_parallel_workers; +RESET max_parallel_workers_per_gather; +RESET enable_seqscan; +RESET enable_bitmapscan; + + -- Secondly test the case of a parallel aggregate combiner function -- returning NULL. For that use normal transition function, but a -- combiner function returning NULL. diff --git a/src/test/regress/sql/equivclass.sql b/src/test/regress/sql/equivclass.sql index 247b0a31055..9a70be2d7aa 100644 --- a/src/test/regress/sql/equivclass.sql +++ b/src/test/regress/sql/equivclass.sql @@ -263,6 +263,22 @@ explain (costs off) explain (costs off) select * from tenk1 where unique1 = unique1 or unique2 = unique2; +-- Test that broken ECs are processed correctly during self join removal. +-- Disable merge joins so that we don't get an error about missing commutator. +-- Test both orientations of the join clause, because only one of them breaks +-- the EC. +set enable_mergejoin to off; + +explain (costs off) + select * from ec0 m join ec0 n on m.ff = n.ff + join ec1 p on m.ff + n.ff = p.f1; + +explain (costs off) + select * from ec0 m join ec0 n on m.ff = n.ff + join ec1 p on p.f1::int8 = (m.ff + n.ff)::int8alias1; + +reset enable_mergejoin; + -- check that we recognize equivalence with dummy domains in the way create temp table undername (f1 name, f2 int); create temp view overview as diff --git a/src/test/regress/sql/incremental_sort.sql b/src/test/regress/sql/incremental_sort.sql index 284a354dbb7..6a0e87c7f64 100644 --- a/src/test/regress/sql/incremental_sort.sql +++ b/src/test/regress/sql/incremental_sort.sql @@ -213,7 +213,7 @@ set parallel_tuple_cost = 0; set max_parallel_workers_per_gather = 2; create table t (a int, b int, c int); -insert into t select mod(i,10),mod(i,10),i from generate_series(1,10000) s(i); +insert into t select mod(i,10),mod(i,10),i from generate_series(1,60000) s(i); create index on t (a); analyze t; diff --git a/src/test/regress/sql/join.sql b/src/test/regress/sql/join.sql index 5fdacce91df..0f6645c0bcd 100644 --- a/src/test/regress/sql/join.sql +++ b/src/test/regress/sql/join.sql @@ -1785,6 +1785,104 @@ select * from select * from int8_tbl x join (int4_tbl x cross join int4_tbl y(ff)) j on q1 = f1; -- ok +-- +-- test that semi- or inner self-joins on a unique column are removed +-- + +-- enable only nestloop to get more predictable plans +set enable_hashjoin to off; +set enable_mergejoin to off; + +create table sj (a int unique, b int); +insert into sj values (1, null), (null, 2), (2, 1); +analyze sj; + +select p.* from sj p, sj q where q.a = p.a and q.b = q.a - 1; + +explain (costs off) +select p.* from sj p, sj q where q.a = p.a and q.b = q.a - 1; + +explain (costs off) +select * from sj p +where exists (select * from sj q + where q.a = p.a and q.b < 10); + +-- Double self-join removal. +-- Use a condition on "b + 1", not on "b", for the second join, so that +-- the equivalence class is different from the first one, and we can +-- test the non-ec code path. +explain (costs off) +select * from sj t1 join sj t2 on t1.a = t2.a and t1.b = t2.b + join sj t3 on t2.a = t3.a and t2.b + 1 = t3.b + 1; + +-- subselect that references the removed relation +explain (costs off) +select t1.a, (select a from sj where a = t2.a and a = t1.a) +from sj t1, sj t2 +where t1.a = t2.a; + +-- self-join under outer join +explain (costs off) +select * from sj x join sj y on x.a = y.a +left join int8_tbl z on x.a = z.q1; + +explain (costs off) +select * from sj x join sj y on x.a = y.a +left join int8_tbl z on y.a = z.q1; + +-- Test that placeholders are updated correctly after join removal +explain (costs off) +select * from (values (1)) x +left join (select coalesce(y.q1, 1) from int8_tbl y + right join sj j1 inner join sj j2 on j1.a = j2.a + on true) z +on true; + +-- update lateral references and range table entry reference +explain (verbose, costs off) +select 1 from (select x.* from sj x, sj y where x.a = y.a) q, + lateral generate_series(1, q.a) gs(i); + +explain (verbose, costs off) +select 1 from (select y.* from sj x, sj y where x.a = y.a) q, + lateral generate_series(1, q.a) gs(i); + +-- Test that a non-EC-derived join clause is processed correctly. Use an +-- outer join so that we can't form an EC. +explain (costs off) select * from sj p join sj q on p.a = q.a + left join sj r on p.a + q.a = r.a; + +-- Check that attr_needed is updated correctly after self-join removal. In +-- this test, k1.b is required at either j1 or j2. If this info is lost, +-- join targetlist for (k1, k2) will not contain k1.b. Use index scan for +-- k1 so that we don't get 'b' from physical tlist used for seqscan. Also +-- disable reordering of joins because this test depends on a particular +-- join tree. +create table sk (a int, b int); +create index sk_a_idx on sk(a); +set join_collapse_limit to 1; +set enable_seqscan to off; +explain (costs off) select 1 from + (sk k1 join sk k2 on k1.a = k2.a) + join (sj j1 join sj j2 on j1.a = j2.a) on j1.b = k1.b; +explain (costs off) select 1 from + (sk k1 join sk k2 on k1.a = k2.a) + join (sj j1 join sj j2 on j1.a = j2.a) on j2.b = k1.b; +reset join_collapse_limit; +reset enable_seqscan; + +-- If index conditions are different for each side, we won't select the same +-- row on both sides, so the join can't be removed. +create table sl(a int, b int); +create unique index sl_ab on sl(a, b); + +explain (costs off) +select * from sl t1, sl t2 +where t1.a = t2.a and t1.b = 1 and t2.b = 2; + +reset enable_hashjoin; +reset enable_mergejoin; + -- -- Test hints given on incorrect column references are useful -- diff --git a/src/test/regress/sql/select.sql b/src/test/regress/sql/select.sql index 019f1e76739..3dd3a606e2a 100644 --- a/src/test/regress/sql/select.sql +++ b/src/test/regress/sql/select.sql @@ -148,6 +148,33 @@ CREATE TEMP TABLE nocols(); INSERT INTO nocols DEFAULT VALUES; SELECT * FROM nocols n, LATERAL (VALUES(n.*)) v; +-- +-- test order by NULLS (FIRST|LAST) +-- + +select unique1, unique2 into onek_with_null from onek; +insert into onek_with_null (unique1,unique2) values (NULL, -1), (NULL, NULL); + + +select * from onek_with_null order by unique1 nulls first , unique2 limit 3; +select * from onek_with_null order by unique1 nulls last , unique2 limit 3; +select * from onek_with_null order by unique1 nulls first , unique2 nulls first limit 3; +select * from onek_with_null order by unique1 nulls last , unique2 nulls first limit 3; +select * from onek_with_null order by unique1 nulls first , unique2 nulls last limit 3; +select * from onek_with_null order by unique1 nulls last , unique2 nulls last limit 3; + +select * from onek_with_null order by unique1 desc nulls first , unique2 desc limit 3; +select * from onek_with_null order by unique1 desc nulls last , unique2 desc limit 3; +select * from onek_with_null order by unique1 desc nulls first , unique2 desc nulls first limit 3; +select * from onek_with_null order by unique1 desc nulls last , unique2 desc nulls first limit 3; +select * from onek_with_null order by unique1 desc nulls first , unique2 desc nulls last limit 3; +select * from onek_with_null order by unique1 desc nulls last , unique2 desc nulls last limit 3; + +select unique1 as u1, unique2 as u2 from onek_with_null order by u1 nulls first , u2 nulls first limit 3; +select unique1 as u1, unique2 as u2 from onek_with_null order by u1 asc nulls first , u2 desc nulls first limit 3; + +drop table onek_with_null; + -- -- Test ORDER BY options -- diff --git a/src/test/regress/sql/stats_ext.sql b/src/test/regress/sql/stats_ext.sql index f0ee4159720..a9b15a1b189 100644 --- a/src/test/regress/sql/stats_ext.sql +++ b/src/test/regress/sql/stats_ext.sql @@ -6,6 +6,8 @@ -- from auto-analyze happening when we didn't expect it. -- +set default_statistics_target=10000; --prevent random subset for joinsel + -- check the number of estimated/actual rows in the top node create function check_estimated_rows(text) returns table (estimated int, actual int) language plpgsql as diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm index 990c223a9b4..ce0998ff648 100644 --- a/src/tools/msvc/Mkvcbuild.pm +++ b/src/tools/msvc/Mkvcbuild.pm @@ -39,8 +39,8 @@ my $contrib_defines = {}; my @contrib_uselibpq = (); my @contrib_uselibpgport = (); my @contrib_uselibpgcommon = (); -my $contrib_extralibs = { 'libpq_pipeline' => ['ws2_32.lib'] }; -my $contrib_extraincludes = {}; +my $contrib_extralibs = { 'libpq_pipeline' => ['ws2_32.lib'], 'mchar' => ['$(ICU46_LIB)\icuin.lib', '$(ICU46_LIB)\icuuc.lib'], 'dbcopies_decoding' => ['$(ICU46_LIB)\icuin.lib', '$(ICU46_LIB)\icuuc.lib'] }; +my $contrib_extraincludes = { 'mchar' => ['$(ICU46_INCLUDE)'], 'dbcopies_decoding' => ['$(ICU46_INCLUDE)', 'contrib/mchar'] }; my $contrib_extrasource = {}; my @contrib_excludes = ( 'bool_plperl', 'commit_ts', @@ -71,11 +71,14 @@ my $frontend_extralibs = { 'pg_amcheck' => ['ws2_32.lib'], 'pg_restore' => ['ws2_32.lib'], 'pgbench' => ['ws2_32.lib'], + 'mchar' => ['$(ICU46_LIB)\icuin.lib', '$(ICU46_LIB)\icuuc.lib'], + 'dbcopies_decoding' => ['$(ICU46_LIB)\icuin.lib', '$(ICU46_LIB)\icuuc.lib'], 'psql' => ['ws2_32.lib'] }; my $frontend_extraincludes = { 'initdb' => ['src/timezone'], - 'psql' => ['src/backend'] + 'psql' => ['src/backend'], + 'dbcopies_decoding' => ['$(ICU46_INCLUDE)', 'contrib/mchar'] }; my $frontend_extrasource = { 'psql' => ['src/bin/psql/psqlscanslash.l'], @@ -1067,6 +1070,8 @@ sub AddContrib # Are there any output data files to build? GenerateContribSqlFiles($n, $mf); + GenerateFulleqSql(); + return; } @@ -1117,6 +1122,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