From 54b1cb7eb784a66680ee18f84e2c4e86fad59e82 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Sat, 8 Jan 2022 14:54:39 -0500 Subject: [PATCH] Fix results of index-only scans on btree_gist char(N) indexes. If contrib/btree_gist is used to make a GIST index on a char(N) (bpchar) column, and that column is retrieved via an index-only scan, what came out had all trailing spaces removed. Since that doesn't happen in any other kind of table scan, this is clearly a bug. The cause is that gbt_bpchar_compress() strips trailing spaces (using rtrim1) before a new index entry is made. That was probably a good idea when this code was first written, but since we invented index-only scans, it's not so good. One answer could be to mark this opclass as incapable of index-only scans. But to do so, we'd need an extension module version bump, followed by manual action by DBAs to install the updated version of btree_gist. And it's not really a desirable place to end up, anyway. Instead, let's fix the code by removing the unwanted space-stripping action and adjusting the opclass's comparison logic to ignore trailing spaces as bpchar normally does. This will not hinder cases that work today, since index searches with this logic will act the same whether trailing spaces are stored or not. It will not by itself fix the problem of getting space-stripped results from index-only scans, of course. Users who care about that can REINDEX affected indexes after installing this update, to immediately replace all improperly-truncated index entries. Otherwise, it can be expected that the index's behavior will change incrementally as old entries are replaced by new ones. Per report from Alexander Lakhin. Back-patch to all supported branches. Discussion: https://postgr.es/m/696c995b-b37f-5526-f45d-04abe713179f@gmail.com --- contrib/btree_gist/btree_text.c | 104 ++++++++++++++++++------- contrib/btree_gist/expected/char.out | 6 +- contrib/btree_gist/expected/char_1.out | 6 +- 3 files changed, 82 insertions(+), 34 deletions(-) diff --git a/contrib/btree_gist/btree_text.c b/contrib/btree_gist/btree_text.c index 8019d11281..be0eac7975 100644 --- a/contrib/btree_gist/btree_text.c +++ b/contrib/btree_gist/btree_text.c @@ -90,6 +90,76 @@ static gbtree_vinfo tinfo = NULL }; +/* bpchar needs its own comparison rules */ + +static bool +gbt_bpchargt(const void *a, const void *b, Oid collation, FmgrInfo *flinfo) +{ + return DatumGetBool(DirectFunctionCall2Coll(bpchargt, + collation, + PointerGetDatum(a), + PointerGetDatum(b))); +} + +static bool +gbt_bpcharge(const void *a, const void *b, Oid collation, FmgrInfo *flinfo) +{ + return DatumGetBool(DirectFunctionCall2Coll(bpcharge, + collation, + PointerGetDatum(a), + PointerGetDatum(b))); +} + +static bool +gbt_bpchareq(const void *a, const void *b, Oid collation, FmgrInfo *flinfo) +{ + return DatumGetBool(DirectFunctionCall2Coll(bpchareq, + collation, + PointerGetDatum(a), + PointerGetDatum(b))); +} + +static bool +gbt_bpcharle(const void *a, const void *b, Oid collation, FmgrInfo *flinfo) +{ + return DatumGetBool(DirectFunctionCall2Coll(bpcharle, + collation, + PointerGetDatum(a), + PointerGetDatum(b))); +} + +static bool +gbt_bpcharlt(const void *a, const void *b, Oid collation, FmgrInfo *flinfo) +{ + return DatumGetBool(DirectFunctionCall2Coll(bpcharlt, + collation, + PointerGetDatum(a), + PointerGetDatum(b))); +} + +static int32 +gbt_bpcharcmp(const void *a, const void *b, Oid collation, FmgrInfo *flinfo) +{ + return DatumGetInt32(DirectFunctionCall2Coll(bpcharcmp, + collation, + PointerGetDatum(a), + PointerGetDatum(b))); +} + +static gbtree_vinfo bptinfo = +{ + gbt_t_bpchar, + 0, + false, + gbt_bpchargt, + gbt_bpcharge, + gbt_bpchareq, + gbt_bpcharle, + gbt_bpcharlt, + gbt_bpcharcmp, + NULL +}; + /************************************************** * Text ops @@ -112,29 +182,8 @@ gbt_text_compress(PG_FUNCTION_ARGS) Datum gbt_bpchar_compress(PG_FUNCTION_ARGS) { - GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); - GISTENTRY *retval; - - if (tinfo.eml == 0) - { - tinfo.eml = pg_database_encoding_max_length(); - } - - if (entry->leafkey) - { - - Datum d = DirectFunctionCall1(rtrim1, entry->key); - GISTENTRY trim; - - gistentryinit(trim, d, - entry->rel, entry->page, - entry->offset, true); - retval = gbt_var_compress(&trim, &tinfo); - } - else - retval = entry; - - PG_RETURN_POINTER(retval); + /* This should never have been distinct from gbt_text_compress */ + return gbt_text_compress(fcinfo); } @@ -179,18 +228,17 @@ gbt_bpchar_consistent(PG_FUNCTION_ARGS) bool retval; GBT_VARKEY *key = (GBT_VARKEY *) DatumGetPointer(entry->key); GBT_VARKEY_R r = gbt_var_key_readable(key); - void *trim = (void *) DatumGetPointer(DirectFunctionCall1(rtrim1, PointerGetDatum(query))); /* All cases served by this function are exact */ *recheck = false; - if (tinfo.eml == 0) + if (bptinfo.eml == 0) { - tinfo.eml = pg_database_encoding_max_length(); + bptinfo.eml = pg_database_encoding_max_length(); } - retval = gbt_var_consistent(&r, trim, strategy, PG_GET_COLLATION(), - GIST_LEAF(entry), &tinfo, fcinfo->flinfo); + retval = gbt_var_consistent(&r, query, strategy, PG_GET_COLLATION(), + GIST_LEAF(entry), &bptinfo, fcinfo->flinfo); PG_RETURN_BOOL(retval); } diff --git a/contrib/btree_gist/expected/char.out b/contrib/btree_gist/expected/char.out index d715c045cc..45cf9f38d6 100644 --- a/contrib/btree_gist/expected/char.out +++ b/contrib/btree_gist/expected/char.out @@ -75,8 +75,8 @@ SELECT * FROM chartmp WHERE a BETWEEN '31a' AND '31c'; (2 rows) SELECT * FROM chartmp WHERE a BETWEEN '31a' AND '31c'; - a ------- - 31b0 + a +---------------------------------- + 31b0 (1 row) diff --git a/contrib/btree_gist/expected/char_1.out b/contrib/btree_gist/expected/char_1.out index 867318002b..7b7824b717 100644 --- a/contrib/btree_gist/expected/char_1.out +++ b/contrib/btree_gist/expected/char_1.out @@ -75,8 +75,8 @@ SELECT * FROM chartmp WHERE a BETWEEN '31a' AND '31c'; (2 rows) SELECT * FROM chartmp WHERE a BETWEEN '31a' AND '31c'; - a ------- - 31b0 + a +---------------------------------- + 31b0 (1 row)