Hi Heikki! On 09.01.2026 22:02, David Geier wrote: > Given that doing the sort on pre-sorted input apparently doesn't add > measurable overhead, according to my benchmark results, we can apply > your patch and leave mine out for the moment being.
I've removed my patch from the patch set in favor of your patch. I've adapted your patch slightly as follows: - I replaced the custom for-loop by qunique() - I switched to sort_template.h which gives a nice speedup as it can do some things more efficiently now where the entries are simple Datums. - I use palloc0_array() to initialize the array to GIN_CAT_NORM_KEY in one go. Your patch together with my changes gives a 20% speedup on a table with arrays of 1000 elements and 10% NULLs. See attached test script. > That's btw. also the reason for why 0002 doesn't show much gain: when > the data is pre-sorted, cmpEntries() is not called as much. This turned out to be not the case. I tested 0002 with the attached script but that neither showed any significant improvements. It's still curious to me because cmpEntries() is called hundreds of millions of times and the disassembly shows that the optimized function indeed directly calls the operator rather than having the indirection via FunctionCall2Coll(). Anyways, I've removed the patch from the patch set for the moment being. >>> I would prefer to change qunique() instead. That would enforce using an >>> adequate comparison function from the get go. There are only ~15 calls >>> to qunique(). So refactoring this should also be a fairly small patch. I >>> can do that if there's agreement for that approach. >> >> Works for me. >> >> At quick glance, most if not all of the qunique() calls call qsort() >> just before qunique(). I wonder if we should have a single "sort and >> deduplicate" function, instead. It could perhaps do some deduplication >> "on the go", or other optimizations. > > If it's just for deduplication purposes and the data doesn't have to end > up sorted, something based on a hash map should be even faster. How > about we start with changing the qunique() comparator signature and as a > 2nd step take a closer look at how to go about providing a function that > does it in one go? Thinking about this some more: ideally we have two functions: something like deduplicateArray() and sortAndDeduplicateArray(). We could initially implement deduplicateArray() on top of sortAndDeduplicateArray(). If we ever find a case that needs optimization, and doesn't require the data to actually end up sorted, we can implement deduplicateArray() e.g. on top of simplehash.h. I'll draft a patch and submit it in a separate thread. > What about the other patches? 0003 and 0007 are also pretty simple and > IMHO uncontroversial while giving decent savings. I've reordered the patches such that the ones that I think are uncontroversial, small and ready to be committed are at the beginning (patches 0001 - 0004). The radix sort and ASCII fast-path patches come last (0005 and 0006). I would like to first concentrate on getting 0001 - 0004 in and then get back to 0005 and 0006. I remeasured the savings of 0001 - 0004, which comes on top of the already committed patch that inlined the comparison function, which gave another ~5%: Data set | Patched (ms) | Master (ms) | Speedup --------------------|--------------|--------------|---------- movies(plot) | 8,058 | 10,311 | 1.27x lineitem(l_comment) | 223,233 | 256,986 | 1.19x -- David Geier
table_with_random_int_arrays.sql
Description: application/sql
From b7deb09e0717ee76d98fd1a40cff4b749bfe1c36 Mon Sep 17 00:00:00 2001 From: David Geier <[email protected]> Date: Fri, 14 Nov 2025 11:37:40 +0100 Subject: [PATCH v3 6/6] Add ASCII fastpath to generate_trgm_only() --- contrib/pg_trgm/trgm_op.c | 124 ++++++++++++++++++++------------------ 1 file changed, 65 insertions(+), 59 deletions(-) diff --git a/contrib/pg_trgm/trgm_op.c b/contrib/pg_trgm/trgm_op.c index 39b586f5b9a..d2087b3a45e 100644 --- a/contrib/pg_trgm/trgm_op.c +++ b/contrib/pg_trgm/trgm_op.c @@ -226,32 +226,6 @@ show_limit(PG_FUNCTION_ARGS) PG_RETURN_FLOAT4(similarity_threshold); } -/* - * Finds first word in string, returns pointer to the word, - * endword points to the character after word - */ -static char * -find_word(char *str, int lenstr, char **endword, int *charlen) -{ - char *beginword = str; - - while (beginword - str < lenstr && !ISWORDCHR(beginword)) - beginword += pg_mblen(beginword); - - if (beginword - str >= lenstr) - return NULL; - - *endword = beginword; - *charlen = 0; - while (*endword - str < lenstr && ISWORDCHR(*endword)) - { - *endword += pg_mblen(*endword); - (*charlen)++; - } - - return beginword; -} - /* * Reduce a trigram (three possibly multi-byte characters) to a trgm, * which is always exactly three bytes. If we have three single-byte @@ -337,58 +311,90 @@ make_trigrams(trgm *tptr, char *str, int bytelen, int charlen) static int generate_trgm_only(trgm *trg, char *str, int slen, TrgmBound *bounds) { - trgm *tptr; - char *buf; - int charlen, - bytelen; - char *bword, - *eword; + trgm *tptr = trg; + char *buf; if (slen + LPADDING + RPADDING < 3 || slen == 0) return 0; - tptr = trg; - - /* Allocate a buffer for case-folded, blank-padded words */ - buf = (char *) palloc(slen * pg_database_encoding_max_length() + 4); + buf = palloc_array(char, slen * pg_database_encoding_max_length() + 4 + 1); + memset(buf, ' ', LPADDING); - if (LPADDING > 0) + for (int i = 0; i < slen; ) { - *buf = ' '; - if (LPADDING > 1) - *(buf + 1) = ' '; - } + int num_bytes = LPADDING; + int num_chars = LPADDING; + char *word; - eword = str; - while ((bword = find_word(eword, slen - (eword - str), &eword, &charlen)) != NULL) - { + /* Extract next word */ + while (i < slen) + { + if ((str[i] & 0x80) == 0) /* Fast path for ASCII-only */ + { + if (isalnum(str[i])) + { #ifdef IGNORECASE - bword = str_tolower(bword, eword - bword, DEFAULT_COLLATION_OID); - bytelen = strlen(bword); + buf[num_bytes++] = pg_ascii_tolower(str[i++]); #else - bytelen = eword - bword; + buf[num_bytes++] = str[i++]; #endif + } + else + { + i++; + break; + } + } + else + { + const int mblen = pg_mblen(str + i); + Assert(mblen >= 2); /* Otherwise, it would be ASCII */ + + if (ISWORDCHR(str + i)) + { + memcpy(buf + num_bytes, str + i, mblen); + num_bytes += mblen; + i += mblen; + } + else + { + i += mblen; + break; + } + } + + num_chars++; + } - memcpy(buf + LPADDING, bword, bytelen); + if (num_chars > LPADDING) + { + memset(buf + num_bytes, ' ', RPADDING); + num_bytes += RPADDING; + num_chars += RPADDING; + word = buf; #ifdef IGNORECASE - pfree(bword); + if (num_chars != num_bytes) + { + word = str_tolower(buf, num_bytes, DEFAULT_COLLATION_OID); + num_bytes = strlen(word); /* String can get shorter from lower-casing */ + } #endif - buf[LPADDING + bytelen] = ' '; - buf[LPADDING + bytelen + 1] = ' '; + if (bounds) + bounds[tptr - trg] |= TRGM_BOUND_LEFT; + + tptr = make_trigrams(tptr, word, num_bytes, num_chars); + + if (bounds) + bounds[tptr - trg - 1] |= TRGM_BOUND_RIGHT; - /* Calculate trigrams marking their bounds if needed */ - if (bounds) - bounds[tptr - trg] |= TRGM_BOUND_LEFT; - tptr = make_trigrams(tptr, buf, bytelen + LPADDING + RPADDING, - charlen + LPADDING + RPADDING); - if (bounds) - bounds[tptr - trg - 1] |= TRGM_BOUND_RIGHT; + if (word != buf) + pfree(word); + } } pfree(buf); - return tptr - trg; } -- 2.51.0
From 24b0f4583bf58e3f5b0a7266cacbf38973801f15 Mon Sep 17 00:00:00 2001 From: David Geier <[email protected]> Date: Tue, 11 Nov 2025 13:18:59 +0100 Subject: [PATCH v3 5/6] Optimize generate_trgm() with radix sort --- contrib/pg_trgm/trgm_op.c | 64 +++++++++++++++++++++++++++++++++------ 1 file changed, 54 insertions(+), 10 deletions(-) diff --git a/contrib/pg_trgm/trgm_op.c b/contrib/pg_trgm/trgm_op.c index d616cf3845f..39b586f5b9a 100644 --- a/contrib/pg_trgm/trgm_op.c +++ b/contrib/pg_trgm/trgm_op.c @@ -163,14 +163,6 @@ CMPTRGM_CHOOSE(const void *a, const void *b) } /* Define our specialized sort function name */ -#define ST_SORT trigram_qsort_signed -#define ST_ELEMENT_TYPE_VOID -#define ST_COMPARE(a, b) CMPTRGM_SIGNED(a, b) -#define ST_SCOPE static -#define ST_DEFINE -#define ST_DECLARE -#include "lib/sort_template.h" - #define ST_SORT trigram_qsort_unsigned #define ST_ELEMENT_TYPE_VOID #define ST_COMPARE(a, b) CMPTRGM_UNSIGNED(a, b) @@ -400,6 +392,58 @@ generate_trgm_only(trgm *trg, char *str, int slen, TrgmBound *bounds) return tptr - trg; } +/* + * Needed to properly handle negative numbers in case char is signed. + */ +static inline unsigned char FlipSign(char x) +{ + return x^0x80; +} + +static void radix_sort_trigrams_signed(trgm *trg, int count) +{ + trgm *buffer = palloc_array(trgm, count); + trgm *starts[256]; + trgm *from = trg; + trgm *to = buffer; + int freqs[3][256]; + + /* + * Compute frequencies to partition the buffer. + */ + memset(freqs, 0, sizeof(freqs)); + + for (int i=0; i<count; i++) + for (int j=0; j<3; j++) + freqs[j][FlipSign(trg[i][j])]++; + + /* + * Do the sorting. Start with last character because that's the is "LSB" + * in a trigram. Avoid unnecessary copies by ping-ponging between the buffers. + */ + for (int i=2; i>=0; i--) + { + trgm *old_from = from; + trgm *next = to; + + for (int j=0; j<256; j++) + { + starts[j] = next; + next += freqs[i][j]; + } + + for (int j=0; j<count; j++) + memcpy(starts[FlipSign(from[j][i])]++, from[j], sizeof(trgm)); + + from = to; + to = old_from; + } + + Assert(to == buffer); + memcpy(trg, buffer, sizeof(trgm) * count); + pfree(buffer); +} + /* * Guard against possible overflow in the palloc requests below. (We * don't worry about the additive constants, since palloc can detect @@ -446,7 +490,7 @@ generate_trgm(char *str, int slen) if (len > 1) { if (GetDefaultCharSignedness()) - trigram_qsort_signed((void *) GETARR(trg), len, sizeof(trgm)); + radix_sort_trigrams_signed((trgm *)GETARR(trg), len); else trigram_qsort_unsigned((void *) GETARR(trg), len, sizeof(trgm)); @@ -998,7 +1042,7 @@ generate_wildcard_trgm(const char *str, int slen) if (len > 1) { if (GetDefaultCharSignedness()) - trigram_qsort_signed((void *) GETARR(trg), len, sizeof(trgm)); + radix_sort_trigrams_signed((trgm *)GETARR(trg), len); else trigram_qsort_unsigned((void *) GETARR(trg), len, sizeof(trgm)); -- 2.51.0
From b73f679c39671fe3b01652864dd67d8d6a732327 Mon Sep 17 00:00:00 2001 From: David Geier <[email protected]> Date: Wed, 12 Nov 2025 14:27:13 +0100 Subject: [PATCH v3 4/6] Faster qunique() comparator in generate_trgm() --- contrib/pg_trgm/trgm_op.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/contrib/pg_trgm/trgm_op.c b/contrib/pg_trgm/trgm_op.c index 6af120fa1ad..d616cf3845f 100644 --- a/contrib/pg_trgm/trgm_op.c +++ b/contrib/pg_trgm/trgm_op.c @@ -139,6 +139,14 @@ CMPTRGM_UNSIGNED(const void *a, const void *b) : CMPPCHAR_UNS(a, b, 2)); } +static inline int +CMPTRGM_EQ(const void *a, const void *b) +{ + char *aa = (char *)a; + char *bb = (char *)b; + return aa[0] != bb[0] || aa[1] != bb[1] || aa[2] != bb[2] ? 1 : 0; +} + /* * This gets called on the first call. It replaces the function pointer so * that subsequent calls are routed directly to the chosen implementation. @@ -438,15 +446,11 @@ generate_trgm(char *str, int slen) if (len > 1) { if (GetDefaultCharSignedness()) - { trigram_qsort_signed((void *) GETARR(trg), len, sizeof(trgm)); - len = qunique(GETARR(trg), len, sizeof(trgm), CMPTRGM_SIGNED); - } else - { trigram_qsort_unsigned((void *) GETARR(trg), len, sizeof(trgm)); - len = qunique(GETARR(trg), len, sizeof(trgm), CMPTRGM_UNSIGNED); - } + + len = qunique(GETARR(trg), len, sizeof(trgm), CMPTRGM_EQ); } SET_VARSIZE(trg, CALCGTSIZE(ARRKEY, len)); @@ -994,15 +998,11 @@ generate_wildcard_trgm(const char *str, int slen) if (len > 1) { if (GetDefaultCharSignedness()) - { trigram_qsort_signed((void *) GETARR(trg), len, sizeof(trgm)); - len = qunique(GETARR(trg), len, sizeof(trgm), CMPTRGM_SIGNED); - } else - { trigram_qsort_unsigned((void *) GETARR(trg), len, sizeof(trgm)); - len = qunique(GETARR(trg), len, sizeof(trgm), CMPTRGM_UNSIGNED); - } + + len = qunique(GETARR(trg), len, sizeof(trgm), CMPTRGM_EQ); } SET_VARSIZE(trg, CALCGTSIZE(ARRKEY, len)); -- 2.51.0
From 29fc859f9a2813c2b23ed18196e7b6f970db63e0 Mon Sep 17 00:00:00 2001 From: David Geier <[email protected]> Date: Mon, 10 Nov 2025 15:40:11 +0100 Subject: [PATCH v3 3/6] Make btint4cmp() branchless --- src/backend/access/nbtree/nbtcompare.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/backend/access/nbtree/nbtcompare.c b/src/backend/access/nbtree/nbtcompare.c index 8425805a292..bf081975e3c 100644 --- a/src/backend/access/nbtree/nbtcompare.c +++ b/src/backend/access/nbtree/nbtcompare.c @@ -60,6 +60,7 @@ #include "utils/fmgrprotos.h" #include "utils/skipsupport.h" #include "utils/sortsupport.h" +#include "common/int.h" #ifdef STRESS_SORT_INT_MIN #define A_LESS_THAN_B INT_MIN @@ -202,12 +203,7 @@ btint4cmp(PG_FUNCTION_ARGS) int32 a = PG_GETARG_INT32(0); int32 b = PG_GETARG_INT32(1); - if (a > b) - PG_RETURN_INT32(A_GREATER_THAN_B); - else if (a == b) - PG_RETURN_INT32(0); - else - PG_RETURN_INT32(A_LESS_THAN_B); + PG_RETURN_INT32(pg_cmp_s32(a, b)); } Datum -- 2.51.0
From 4eb42c4ff3869c1c18a883faf4071e98e4fd6eca Mon Sep 17 00:00:00 2001 From: David Geier <[email protected]> Date: Mon, 10 Nov 2025 13:35:11 +0100 Subject: [PATCH v3 2/6] Optimize generate_trgm() with sort_template.h --- contrib/pg_trgm/trgm_op.c | 47 ++++++++++++++++++++++++++++++--------- 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/contrib/pg_trgm/trgm_op.c b/contrib/pg_trgm/trgm_op.c index 81182a15e07..6af120fa1ad 100644 --- a/contrib/pg_trgm/trgm_op.c +++ b/contrib/pg_trgm/trgm_op.c @@ -154,6 +154,23 @@ CMPTRGM_CHOOSE(const void *a, const void *b) return CMPTRGM(a, b); } +/* Define our specialized sort function name */ +#define ST_SORT trigram_qsort_signed +#define ST_ELEMENT_TYPE_VOID +#define ST_COMPARE(a, b) CMPTRGM_SIGNED(a, b) +#define ST_SCOPE static +#define ST_DEFINE +#define ST_DECLARE +#include "lib/sort_template.h" + +#define ST_SORT trigram_qsort_unsigned +#define ST_ELEMENT_TYPE_VOID +#define ST_COMPARE(a, b) CMPTRGM_UNSIGNED(a, b) +#define ST_SCOPE static +#define ST_DEFINE +#define ST_DECLARE +#include "lib/sort_template.h" + /* * Deprecated function. * Use "pg_trgm.similarity_threshold" GUC variable instead of this function. @@ -209,12 +226,6 @@ show_limit(PG_FUNCTION_ARGS) PG_RETURN_FLOAT4(similarity_threshold); } -static int -comp_trgm(const void *a, const void *b) -{ - return CMPTRGM(a, b); -} - /* * Finds first word in string, returns pointer to the word, * endword points to the character after word @@ -426,8 +437,16 @@ generate_trgm(char *str, int slen) */ if (len > 1) { - qsort(GETARR(trg), len, sizeof(trgm), comp_trgm); - len = qunique(GETARR(trg), len, sizeof(trgm), comp_trgm); + if (GetDefaultCharSignedness()) + { + trigram_qsort_signed((void *) GETARR(trg), len, sizeof(trgm)); + len = qunique(GETARR(trg), len, sizeof(trgm), CMPTRGM_SIGNED); + } + else + { + trigram_qsort_unsigned((void *) GETARR(trg), len, sizeof(trgm)); + len = qunique(GETARR(trg), len, sizeof(trgm), CMPTRGM_UNSIGNED); + } } SET_VARSIZE(trg, CALCGTSIZE(ARRKEY, len)); @@ -974,8 +993,16 @@ generate_wildcard_trgm(const char *str, int slen) */ if (len > 1) { - qsort(GETARR(trg), len, sizeof(trgm), comp_trgm); - len = qunique(GETARR(trg), len, sizeof(trgm), comp_trgm); + if (GetDefaultCharSignedness()) + { + trigram_qsort_signed((void *) GETARR(trg), len, sizeof(trgm)); + len = qunique(GETARR(trg), len, sizeof(trgm), CMPTRGM_SIGNED); + } + else + { + trigram_qsort_unsigned((void *) GETARR(trg), len, sizeof(trgm)); + len = qunique(GETARR(trg), len, sizeof(trgm), CMPTRGM_UNSIGNED); + } } SET_VARSIZE(trg, CALCGTSIZE(ARRKEY, len)); -- 2.51.0
From 527e03f05ec4513fcaa59cef7d4c9c3d38ea0e18 Mon Sep 17 00:00:00 2001 From: David Geier <[email protected]> Date: Wed, 14 Jan 2026 10:54:32 +0100 Subject: [PATCH v3 1/6] Optimize sort and deduplication in ginExtractEntries --- src/backend/access/gin/ginutil.c | 152 +++++++++++++------------------ src/include/access/gin_private.h | 2 +- 2 files changed, 66 insertions(+), 88 deletions(-) diff --git a/src/backend/access/gin/ginutil.c b/src/backend/access/gin/ginutil.c index d205093e21d..5be5e22487f 100644 --- a/src/backend/access/gin/ginutil.c +++ b/src/backend/access/gin/ginutil.c @@ -28,6 +28,7 @@ #include "utils/index_selfuncs.h" #include "utils/rel.h" #include "utils/typcache.h" +#include "lib/qunique.h" /* @@ -387,19 +388,6 @@ GinInitMetabuffer(Buffer b) ((char *) metadata + sizeof(GinMetaPageData)) - (char *) page; } -/* - * Support for sorting key datums in ginExtractEntries - * - * Note: we only have to worry about null and not-null keys here; - * ginExtractEntries never generates more than one placeholder null, - * so it doesn't have to sort those. - */ -typedef struct -{ - Datum datum; - bool isnull; -} keyEntryData; - typedef struct { FmgrInfo *cmpDatumFunc; @@ -410,24 +398,14 @@ typedef struct static int cmpEntries(const void *a, const void *b, void *arg) { - const keyEntryData *aa = (const keyEntryData *) a; - const keyEntryData *bb = (const keyEntryData *) b; + const Datum *aa = (const Datum *) a; + const Datum *bb = (const Datum *) b; cmpEntriesArg *data = (cmpEntriesArg *) arg; int res; - if (aa->isnull) - { - if (bb->isnull) - res = 0; /* NULL "=" NULL */ - else - res = 1; /* NULL ">" not-NULL */ - } - else if (bb->isnull) - res = -1; /* not-NULL "<" NULL */ - else - res = DatumGetInt32(FunctionCall2Coll(data->cmpDatumFunc, - data->collation, - aa->datum, bb->datum)); + res = DatumGetInt32(FunctionCall2Coll(data->cmpDatumFunc, + data->collation, + *aa, *bb)); /* * Detect if we have any duplicates. If there are equal keys, qsort must @@ -440,6 +418,14 @@ cmpEntries(const void *a, const void *b, void *arg) return res; } +#define ST_SORT qsort_arg_entries +#define ST_ELEMENT_TYPE Datum +#define ST_COMPARE_ARG_TYPE cmpEntriesArg +#define ST_COMPARE(a, b, arg) cmpEntries(a, b, arg) +#define ST_SCOPE static +#define ST_DEFINE +#define ST_DECLARE +#include "lib/sort_template.h" /* * Extract the index key values from an indexable item @@ -450,11 +436,13 @@ cmpEntries(const void *a, const void *b, void *arg) Datum * ginExtractEntries(GinState *ginstate, OffsetNumber attnum, Datum value, bool isNull, - int32 *nentries, GinNullCategory **categories) + int32 *nentries_p, GinNullCategory **categories_p) { Datum *entries; bool *nullFlags; - int32 i; + GinNullCategory *categories; + bool hasNull; + int32 nentries; /* * We don't call the extractValueFn on a null item. Instead generate a @@ -462,42 +450,60 @@ ginExtractEntries(GinState *ginstate, OffsetNumber attnum, */ if (isNull) { - *nentries = 1; + *nentries_p = 1; entries = palloc_object(Datum); entries[0] = (Datum) 0; - *categories = palloc_object(GinNullCategory); - (*categories)[0] = GIN_CAT_NULL_ITEM; + *categories_p = palloc_object(GinNullCategory); + (*categories_p)[0] = GIN_CAT_NULL_ITEM; return entries; } /* OK, call the opclass's extractValueFn */ nullFlags = NULL; /* in case extractValue doesn't set it */ + nentries = 0; entries = (Datum *) DatumGetPointer(FunctionCall3Coll(&ginstate->extractValueFn[attnum - 1], ginstate->supportCollation[attnum - 1], value, - PointerGetDatum(nentries), + PointerGetDatum(&nentries), PointerGetDatum(&nullFlags))); /* * Generate a placeholder if the item contained no keys. */ - if (entries == NULL || *nentries <= 0) + if (entries == NULL || nentries <= 0) { - *nentries = 1; + *nentries_p = 1; entries = palloc_object(Datum); entries[0] = (Datum) 0; - *categories = palloc_object(GinNullCategory); - (*categories)[0] = GIN_CAT_EMPTY_ITEM; + *categories_p = palloc_object(GinNullCategory); + (*categories_p)[0] = GIN_CAT_EMPTY_ITEM; return entries; } /* - * If the extractValueFn didn't create a nullFlags array, create one, - * assuming that everything's non-null. + * Scan the items for any NULLs. All NULLs are considered equal, so we + * just need to check and remember if there are any. We remove them from + * the array here, and if necessary, put back one NULL entry to represent + * them all after deduplication. */ - if (nullFlags == NULL) - nullFlags = (bool *) palloc0(*nentries * sizeof(bool)); + hasNull = false; + if (nullFlags) + { + int32 numNonNulls = 0; + + for (int32 i = 0; i < nentries; i++) + { + if (nullFlags[i]) + hasNull = true; + else + { + entries[numNonNulls] = entries[i]; + numNonNulls++; + } + } + nentries = numNonNulls; + } /* * If there's more than one key, sort and unique-ify. @@ -506,63 +512,35 @@ ginExtractEntries(GinState *ginstate, OffsetNumber attnum, * pretty bad too. For small numbers of keys it'd likely be better to use * a simple insertion sort. */ - if (*nentries > 1) + if (nentries > 1) { - keyEntryData *keydata; cmpEntriesArg arg; - - keydata = palloc_array(keyEntryData, *nentries); - for (i = 0; i < *nentries; i++) - { - keydata[i].datum = entries[i]; - keydata[i].isnull = nullFlags[i]; - } - arg.cmpDatumFunc = &ginstate->compareFn[attnum - 1]; arg.collation = ginstate->supportCollation[attnum - 1]; arg.haveDups = false; - qsort_arg(keydata, *nentries, sizeof(keyEntryData), - cmpEntries, &arg); - if (arg.haveDups) - { - /* there are duplicates, must get rid of 'em */ - int32 j; - - entries[0] = keydata[0].datum; - nullFlags[0] = keydata[0].isnull; - j = 1; - for (i = 1; i < *nentries; i++) - { - if (cmpEntries(&keydata[i - 1], &keydata[i], &arg) != 0) - { - entries[j] = keydata[i].datum; - nullFlags[j] = keydata[i].isnull; - j++; - } - } - *nentries = j; - } - else - { - /* easy, no duplicates */ - for (i = 0; i < *nentries; i++) - { - entries[i] = keydata[i].datum; - nullFlags[i] = keydata[i].isnull; - } - } + qsort_arg_entries(entries, nentries, &arg); - pfree(keydata); + if (arg.haveDups) + nentries = qunique_arg(entries, nentries, sizeof(Datum), cmpEntries, &arg); } /* - * Create GinNullCategory representation from nullFlags. + * Create GinNullCategory representation. */ - *categories = (GinNullCategory *) palloc0(*nentries * sizeof(GinNullCategory)); - for (i = 0; i < *nentries; i++) - (*categories)[i] = (nullFlags[i] ? GIN_CAT_NULL_KEY : GIN_CAT_NORM_KEY); + StaticAssertStmt(GIN_CAT_NORM_KEY == 0, "Assuming GIN_CAT_NORM_KEY=0"); + categories = palloc0_array(GinNullCategory, nentries + (hasNull ? 1 : 0)); + + /* Put back a NULL entry, if there were any */ + if (hasNull) + { + entries[nentries] = (Datum) 0; + categories[nentries] = GIN_CAT_NULL_KEY; + nentries++; + } + *nentries_p = nentries; + *categories_p = categories; return entries; } diff --git a/src/include/access/gin_private.h b/src/include/access/gin_private.h index e155045ce8a..1e97e6cb3c9 100644 --- a/src/include/access/gin_private.h +++ b/src/include/access/gin_private.h @@ -99,7 +99,7 @@ extern void GinInitPage(Page page, uint32 f, Size pageSize); extern void GinInitMetabuffer(Buffer b); extern Datum *ginExtractEntries(GinState *ginstate, OffsetNumber attnum, Datum value, bool isNull, - int32 *nentries, GinNullCategory **categories); + int32 *nentries_p, GinNullCategory **categories_p); extern OffsetNumber gintuple_get_attrnum(GinState *ginstate, IndexTuple tuple); extern Datum gintuple_get_key(GinState *ginstate, IndexTuple tuple, -- 2.51.0
