mirror of
https://github.com/postgres/postgres.git
synced 2026-07-06 16:25:55 -04:00
Fix LIKE/regex optimization for indexscan with exact-match pattern.
Commit 85b7efa1c introduced support for LIKE with non-deterministic
collations. By moving some conditionals around, it accidentally broke
the optimization for converting a LIKE or regex exact-match pattern
to an equality indexqual when the index collation doesn't match the
expression collation. That should be allowed if the expression
collation is deterministic. This patch re-introduces the optimization
for that common case.
One important beneficiary of this optimization is the "\d tablename"
command in psql. Without this fix that will do a seqscan on pg_class
instead of an index point lookup.
Reported-by: Andres Freund <andres@anarazel.de>
Author: Jelte Fennema-Nio <postgres@jeltef.nl>
Reviewed-by: Tom Lane <tgl@sss.pgh.pa.us>
Discussion: https://postgr.es/m/DHBQIZX8SZVI.ZX614ZMFL645@jeltef.nl
Backpatch-through: 18
This commit is contained in:
parent
2ddc456621
commit
67cf73ddbe
5 changed files with 82 additions and 4 deletions
|
|
@ -69,6 +69,10 @@ typedef enum
|
|||
Pattern_Prefix_None, Pattern_Prefix_Partial, Pattern_Prefix_Exact,
|
||||
} Pattern_Prefix_Status;
|
||||
|
||||
/* non-collatable comparisons, eg for bytea, are always deterministic */
|
||||
#define NONDETERMINISTIC(coll) \
|
||||
(OidIsValid(coll) && !get_collation_isdeterministic(coll))
|
||||
|
||||
static Node *like_regex_support(Node *rawreq, Pattern_Type ptype);
|
||||
static List *match_pattern_prefix(Node *leftop,
|
||||
Node *rightop,
|
||||
|
|
@ -381,12 +385,22 @@ match_pattern_prefix(Node *leftop,
|
|||
* us to not be concerned with specific opclasses (except for the legacy
|
||||
* "pattern" cases); any index that correctly implements the operators
|
||||
* will work.
|
||||
*
|
||||
* This case will work for LIKE/regex expressions with nondeterministic
|
||||
* collation, so long as the index's collation is the same. If the
|
||||
* expression's collation is deterministic, we can even use an index whose
|
||||
* collation differs from the expression's. All deterministic collations
|
||||
* agree on equality (it's bitwise), while we assume that an index with
|
||||
* nondeterministic collation will return a superset of the bitwise-equal
|
||||
* entries. Since the "=" indexqual is marked as lossy by default, we'll
|
||||
* apply the LIKE/regex operator as a recheck, and that will filter out
|
||||
* any non-matching entries.
|
||||
*/
|
||||
if (pstatus == Pattern_Prefix_Exact)
|
||||
{
|
||||
if (!op_in_opfamily(eqopr, opfamily))
|
||||
return NIL;
|
||||
if (indexcollation != expr_coll)
|
||||
if (indexcollation != expr_coll && NONDETERMINISTIC(expr_coll))
|
||||
return NIL;
|
||||
expr = make_opclause(eqopr, BOOLOID, false,
|
||||
(Expr *) leftop, (Expr *) prefix,
|
||||
|
|
@ -400,10 +414,8 @@ match_pattern_prefix(Node *leftop,
|
|||
* expression collation is nondeterministic. The optimized equality or
|
||||
* prefix tests use bytewise comparisons, which is not consistent with
|
||||
* nondeterministic collations.
|
||||
*
|
||||
* expr_coll is not set for a non-collation-aware data type such as bytea.
|
||||
*/
|
||||
if (expr_coll && !get_collation_isdeterministic(expr_coll))
|
||||
if (NONDETERMINISTIC(expr_coll))
|
||||
return NIL;
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -2084,6 +2084,29 @@ SELECT string_to_array('ABCDEFGHI' COLLATE case_insensitive, NULL, 'b');
|
|||
{A,NULL,C,D,E,F,G,H,I}
|
||||
(1 row)
|
||||
|
||||
-- These queries should be able to use the index on test1ci.x:
|
||||
SET enable_seqscan = off;
|
||||
SET enable_indexonlyscan = off;
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT * FROM test1ci WHERE x ~ '^abc$' COLLATE "C";
|
||||
QUERY PLAN
|
||||
-------------------------------------------
|
||||
Index Scan using test1ci_x_idx on test1ci
|
||||
Index Cond: (x = 'abc'::text)
|
||||
Filter: (x ~ '^abc$'::text COLLATE "C")
|
||||
(3 rows)
|
||||
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT * FROM test1ci WHERE x LIKE 'abc' COLLATE case_insensitive;
|
||||
QUERY PLAN
|
||||
-------------------------------------------------------
|
||||
Index Scan using test1ci_x_idx on test1ci
|
||||
Index Cond: (x = 'abc'::text)
|
||||
Filter: (x ~~ 'abc'::text COLLATE case_insensitive)
|
||||
(3 rows)
|
||||
|
||||
RESET enable_seqscan;
|
||||
RESET enable_indexonlyscan;
|
||||
-- Test HAVING-to-WHERE pushdown with nondeterministic collations.
|
||||
-- When a HAVING clause uses a different collation than the GROUP BY's
|
||||
-- nondeterministic collation, it must not be pushed to WHERE, otherwise
|
||||
|
|
|
|||
|
|
@ -768,6 +768,29 @@ DETAIL: LOCALE cannot be specified together with LC_COLLATE or LC_CTYPE.
|
|||
CREATE COLLATION coll_dup_chk (FROM = "C", VERSION = "1");
|
||||
ERROR: conflicting or redundant options
|
||||
DETAIL: FROM cannot be specified together with any other options.
|
||||
-- Regex exact-match optimization should use an index even when the expression
|
||||
-- and index have different collations, so long as the expression's collation
|
||||
-- is deterministic. This example tests what we want because the optimizer
|
||||
-- does not perceive "C" collation (used by the system catalogs) as identical
|
||||
-- to "POSIX" collation.
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT * FROM pg_class WHERE relname ~ '^pg_class$' COLLATE "POSIX";
|
||||
QUERY PLAN
|
||||
----------------------------------------------------------
|
||||
Index Scan using pg_class_relname_nsp_index on pg_class
|
||||
Index Cond: (relname = 'pg_class'::text)
|
||||
Filter: (relname ~ '^pg_class$'::text COLLATE "POSIX")
|
||||
(3 rows)
|
||||
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT * FROM pg_class WHERE relname LIKE 'pg\_class' COLLATE "POSIX";
|
||||
QUERY PLAN
|
||||
----------------------------------------------------------
|
||||
Index Scan using pg_class_relname_nsp_index on pg_class
|
||||
Index Cond: (relname = 'pg_class'::text)
|
||||
Filter: (relname ~~ 'pg\_class'::text COLLATE "POSIX")
|
||||
(3 rows)
|
||||
|
||||
--
|
||||
-- Clean up. Many of these table names will be re-used if the user is
|
||||
-- trying to run any platform-specific collation tests later, so we
|
||||
|
|
|
|||
|
|
@ -745,6 +745,16 @@ CREATE UNIQUE INDEX ON test3ci (x); -- error
|
|||
SELECT string_to_array('ABC,DEF,GHI' COLLATE case_insensitive, ',', 'abc');
|
||||
SELECT string_to_array('ABCDEFGHI' COLLATE case_insensitive, NULL, 'b');
|
||||
|
||||
-- These queries should be able to use the index on test1ci.x:
|
||||
SET enable_seqscan = off;
|
||||
SET enable_indexonlyscan = off;
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT * FROM test1ci WHERE x ~ '^abc$' COLLATE "C";
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT * FROM test1ci WHERE x LIKE 'abc' COLLATE case_insensitive;
|
||||
RESET enable_seqscan;
|
||||
RESET enable_indexonlyscan;
|
||||
|
||||
-- Test HAVING-to-WHERE pushdown with nondeterministic collations.
|
||||
-- When a HAVING clause uses a different collation than the GROUP BY's
|
||||
-- nondeterministic collation, it must not be pushed to WHERE, otherwise
|
||||
|
|
|
|||
|
|
@ -302,6 +302,16 @@ CREATE COLLATION coll_dup_chk (LC_CTYPE = "POSIX", LOCALE = '');
|
|||
-- FROM conflicts with any other option
|
||||
CREATE COLLATION coll_dup_chk (FROM = "C", VERSION = "1");
|
||||
|
||||
-- Regex exact-match optimization should use an index even when the expression
|
||||
-- and index have different collations, so long as the expression's collation
|
||||
-- is deterministic. This example tests what we want because the optimizer
|
||||
-- does not perceive "C" collation (used by the system catalogs) as identical
|
||||
-- to "POSIX" collation.
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT * FROM pg_class WHERE relname ~ '^pg_class$' COLLATE "POSIX";
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT * FROM pg_class WHERE relname LIKE 'pg\_class' COLLATE "POSIX";
|
||||
|
||||
--
|
||||
-- Clean up. Many of these table names will be re-used if the user is
|
||||
-- trying to run any platform-specific collation tests later, so we
|
||||
|
|
|
|||
Loading…
Reference in a new issue