postgres.git / summary / log / commit / refs

commit    dfd8e6c73eeaa8a4d53851ba01e8d7b1c0bef0e3
Author:   Masahiko Sawada <msawada@postgresql.org>
Date:     Fri Feb 21 18:27:39 2025 +0000

    Fix an issue with index scan using pg_trgm due to char signedness on different architectures.
    
    GIN and GiST indexes utilizing pg_trgm's opclasses store sorted
    trigrams within index tuples. When comparing and sorting each trigram,
    pg_trgm treats each character as a 'char[3]' type in C. However, the
    char type in C can be interpreted as either signed char or unsigned
    char, depending on the platform, if the signedness is not explicitly
    specified. Consequently, during replication between different CPU
    architectures, there was an issue where index scans on standby servers
    could not locate matching index tuples due to the differing treatment
    of character signedness.
    
    This change introduces comparison functions for trgm that explicitly
    handle signed char and unsigned char. The appropriate comparison
    function will be dynamically selected based on the character
    signedness stored in the control file. Therefore, upgraded clusters
    can utilize the indexes without rebuilding, provided the cluster
    upgrade occurs on platforms with the same character signedness as the
    original cluster initialization.
    
    The default char signedness information was introduced in 44fe30fdab6,
    so no backpatch.
    
    Reviewed-by: Noah Misch <noah@leadboat.com>
    Discussion: https://postgr.es/m/CB11ADBC-0C3F-4FE0-A678-666EE80CBB07%40amazon.com


contrib/pg_trgm/trgm.h | 5 +---- contrib/pg_trgm/trgm_op.c | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/contrib/pg_trgm/trgm.h b/contrib/pg_trgm/trgm.h index 10827563694..ca017585369 100644 --- a/contrib/pg_trgm/trgm.h +++ b/contrib/pg_trgm/trgm.h @@ -40,15 +40,12 @@ typedef char trgm[3]; -#define CMPCHAR(a,b) ( ((a)==(b)) ? 0 : ( ((a)<(b)) ? -1 : 1 ) ) -#define CMPPCHAR(a,b,i) CMPCHAR( *(((const char*)(a))+i), *(((const char*)(b))+i) ) -#define CMPTRGM(a,b) ( CMPPCHAR(a,b,0) ? CMPPCHAR(a,b,0) : ( CMPPCHAR(a,b,1) ? CMPPCHAR(a,b,1) : CMPPCHAR(a,b,2) ) ) - #define CPTRGM(a,b) do { \ *(((char*)(a))+0) = *(((char*)(b))+0); \ *(((char*)(a))+1) = *(((char*)(b))+1); \ *(((char*)(a))+2) = *(((char*)(b))+2); \ } while(0) +extern int (*CMPTRGM) (const void *a, const void *b); #define ISWORDCHR(c) (t_isalnum(c)) #define ISPRINTABLECHAR(a) ( isascii( *(unsigned char*)(a) ) && (isalnum( *(unsigned char*)(a) ) || *(unsigned char*)(a)==' ') ) diff --git a/contrib/pg_trgm/trgm_op.c b/contrib/pg_trgm/trgm_op.c index d0833b3e4a1..94b9015fd67 100644 --- a/contrib/pg_trgm/trgm_op.c +++ b/contrib/pg_trgm/trgm_op.c @@ -42,6 +42,9 @@ PG_FUNCTION_INFO_V1(strict_word_similarity_commutator_op); PG_FUNCTION_INFO_V1(strict_word_similarity_dist_op); PG_FUNCTION_INFO_V1(strict_word_similarity_dist_commutator_op); +static int CMPTRGM_CHOOSE(const void *a, const void *b); +int (*CMPTRGM) (const void *a, const void *b) = CMPTRGM_CHOOSE; + /* Trigram with position */ typedef struct { @@ -107,6 +110,47 @@ _PG_init(void) MarkGUCPrefixReserved("pg_trgm"); } +#define CMPCHAR(a,b) ( ((a)==(b)) ? 0 : ( ((a)<(b)) ? -1 : 1 ) ) + +/* + * Functions for comparing two trgms while treating each char as "signed char" or + * "unsigned char". + */ +static inline int +CMPTRGM_SIGNED(const void *a, const void *b) +{ +#define CMPPCHAR_S(a,b,i) CMPCHAR( *(((const signed char*)(a))+i), *(((const signed char*)(b))+i) ) + + return CMPPCHAR_S(a, b, 0) ? CMPPCHAR_S(a, b, 0) + : (CMPPCHAR_S(a, b, 1) ? CMPPCHAR_S(a, b, 1) + : CMPPCHAR_S(a, b, 2)); +} + +static inline int +CMPTRGM_UNSIGNED(const void *a, const void *b) +{ +#define CMPPCHAR_UNS(a,b,i) CMPCHAR( *(((const unsigned char*)(a))+i), *(((const unsigned char*)(b))+i) ) + + return CMPPCHAR_UNS(a, b, 0) ? CMPPCHAR_UNS(a, b, 0) + : (CMPPCHAR_UNS(a, b, 1) ? CMPPCHAR_UNS(a, b, 1) + : CMPPCHAR_UNS(a, b, 2)); +} + +/* + * This gets called on the first call. It replaces the function pointer so + * that subsequent calls are routed directly to the chosen implementation. + */ +static int +CMPTRGM_CHOOSE(const void *a, const void *b) +{ + if (GetDefaultCharSignedness()) + CMPTRGM = CMPTRGM_SIGNED; + else + CMPTRGM = CMPTRGM_UNSIGNED; + + return CMPTRGM(a, b); +} + /* * Deprecated function. * Use "pg_trgm.similarity_threshold" GUC variable instead of this function. [parent: 1aab6805919b]