mirror of
https://github.com/postgres/postgres.git
synced 2025-05-21 15:54:08 +03:00
Add a planner support function for starts_with().
This fills in some gaps in planner support for starts_with() and the equivalent ^@ operator: * A condition such as "textcol ^@ constant" can now use a regular btree index, not only an SP-GiST index, so long as the index's collation is C. (This works just like "textcol LIKE 'foo%'".) * "starts_with(textcol, constant)" can be optimized the same as "textcol ^@ constant". * Fixed-prefix LIKE and regex patterns are now more like starts_with() in another way: if you apply one to an SPGiST-indexed column, you'll get an index condition using ^@ rather than two index conditions with >= and <. Per a complaint from Shay Rojansky. Patch by me; thanks to Nathan Bossart for review. Discussion: https://postgr.es/m/232599.1633800229@sss.pgh.pa.us
This commit is contained in:
parent
248c3a937d
commit
a148f8bc04
@ -143,6 +143,14 @@ texticregexeq_support(PG_FUNCTION_ARGS)
|
|||||||
PG_RETURN_POINTER(like_regex_support(rawreq, Pattern_Type_Regex_IC));
|
PG_RETURN_POINTER(like_regex_support(rawreq, Pattern_Type_Regex_IC));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Datum
|
||||||
|
text_starts_with_support(PG_FUNCTION_ARGS)
|
||||||
|
{
|
||||||
|
Node *rawreq = (Node *) PG_GETARG_POINTER(0);
|
||||||
|
|
||||||
|
PG_RETURN_POINTER(like_regex_support(rawreq, Pattern_Type_Prefix));
|
||||||
|
}
|
||||||
|
|
||||||
/* Common code for the above */
|
/* Common code for the above */
|
||||||
static Node *
|
static Node *
|
||||||
like_regex_support(Node *rawreq, Pattern_Type ptype)
|
like_regex_support(Node *rawreq, Pattern_Type ptype)
|
||||||
@ -246,6 +254,7 @@ match_pattern_prefix(Node *leftop,
|
|||||||
Oid eqopr;
|
Oid eqopr;
|
||||||
Oid ltopr;
|
Oid ltopr;
|
||||||
Oid geopr;
|
Oid geopr;
|
||||||
|
Oid preopr = InvalidOid;
|
||||||
bool collation_aware;
|
bool collation_aware;
|
||||||
Expr *expr;
|
Expr *expr;
|
||||||
FmgrInfo ltproc;
|
FmgrInfo ltproc;
|
||||||
@ -302,14 +311,22 @@ match_pattern_prefix(Node *leftop,
|
|||||||
switch (ldatatype)
|
switch (ldatatype)
|
||||||
{
|
{
|
||||||
case TEXTOID:
|
case TEXTOID:
|
||||||
if (opfamily == TEXT_PATTERN_BTREE_FAM_OID ||
|
if (opfamily == TEXT_PATTERN_BTREE_FAM_OID)
|
||||||
opfamily == TEXT_SPGIST_FAM_OID)
|
|
||||||
{
|
{
|
||||||
eqopr = TextEqualOperator;
|
eqopr = TextEqualOperator;
|
||||||
ltopr = TextPatternLessOperator;
|
ltopr = TextPatternLessOperator;
|
||||||
geopr = TextPatternGreaterEqualOperator;
|
geopr = TextPatternGreaterEqualOperator;
|
||||||
collation_aware = false;
|
collation_aware = false;
|
||||||
}
|
}
|
||||||
|
else if (opfamily == TEXT_SPGIST_FAM_OID)
|
||||||
|
{
|
||||||
|
eqopr = TextEqualOperator;
|
||||||
|
ltopr = TextPatternLessOperator;
|
||||||
|
geopr = TextPatternGreaterEqualOperator;
|
||||||
|
/* This opfamily has direct support for prefixing */
|
||||||
|
preopr = TextPrefixOperator;
|
||||||
|
collation_aware = false;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
eqopr = TextEqualOperator;
|
eqopr = TextEqualOperator;
|
||||||
@ -360,20 +377,6 @@ match_pattern_prefix(Node *leftop,
|
|||||||
return NIL;
|
return NIL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* If necessary, verify that the index's collation behavior is compatible.
|
|
||||||
* For an exact-match case, we don't have to be picky. Otherwise, insist
|
|
||||||
* that the index collation be "C". Note that here we are looking at the
|
|
||||||
* index's collation, not the expression's collation -- this test is *not*
|
|
||||||
* dependent on the LIKE/regex operator's collation.
|
|
||||||
*/
|
|
||||||
if (collation_aware)
|
|
||||||
{
|
|
||||||
if (!(pstatus == Pattern_Prefix_Exact ||
|
|
||||||
lc_collate_is_c(indexcollation)))
|
|
||||||
return NIL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If necessary, coerce the prefix constant to the right type. The given
|
* If necessary, coerce the prefix constant to the right type. The given
|
||||||
* prefix constant is either text or bytea type, therefore the only case
|
* prefix constant is either text or bytea type, therefore the only case
|
||||||
@ -409,8 +412,31 @@ match_pattern_prefix(Node *leftop,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Otherwise, we have a nonempty required prefix of the values.
|
* Otherwise, we have a nonempty required prefix of the values. Some
|
||||||
*
|
* opclasses support prefix checks directly, otherwise we'll try to
|
||||||
|
* generate a range constraint.
|
||||||
|
*/
|
||||||
|
if (OidIsValid(preopr) && op_in_opfamily(preopr, opfamily))
|
||||||
|
{
|
||||||
|
expr = make_opclause(preopr, BOOLOID, false,
|
||||||
|
(Expr *) leftop, (Expr *) prefix,
|
||||||
|
InvalidOid, indexcollation);
|
||||||
|
result = list_make1(expr);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Since we need a range constraint, it's only going to work reliably if
|
||||||
|
* the index is collation-insensitive or has "C" collation. Note that
|
||||||
|
* here we are looking at the index's collation, not the expression's
|
||||||
|
* collation -- this test is *not* dependent on the LIKE/regex operator's
|
||||||
|
* collation.
|
||||||
|
*/
|
||||||
|
if (collation_aware &&
|
||||||
|
!lc_collate_is_c(indexcollation))
|
||||||
|
return NIL;
|
||||||
|
|
||||||
|
/*
|
||||||
* We can always say "x >= prefix".
|
* We can always say "x >= prefix".
|
||||||
*/
|
*/
|
||||||
if (!op_in_opfamily(geopr, opfamily))
|
if (!op_in_opfamily(geopr, opfamily))
|
||||||
@ -1165,7 +1191,6 @@ pattern_fixed_prefix(Const *patt, Pattern_Type ptype, Oid collation,
|
|||||||
case Pattern_Type_Prefix:
|
case Pattern_Type_Prefix:
|
||||||
/* Prefix type work is trivial. */
|
/* Prefix type work is trivial. */
|
||||||
result = Pattern_Prefix_Partial;
|
result = Pattern_Prefix_Partial;
|
||||||
*rest_selec = 1.0; /* all */
|
|
||||||
*prefix = makeConst(patt->consttype,
|
*prefix = makeConst(patt->consttype,
|
||||||
patt->consttypmod,
|
patt->consttypmod,
|
||||||
patt->constcollid,
|
patt->constcollid,
|
||||||
@ -1175,6 +1200,8 @@ pattern_fixed_prefix(Const *patt, Pattern_Type ptype, Oid collation,
|
|||||||
patt->constlen),
|
patt->constlen),
|
||||||
patt->constisnull,
|
patt->constisnull,
|
||||||
patt->constbyval);
|
patt->constbyval);
|
||||||
|
if (rest_selec != NULL)
|
||||||
|
*rest_selec = 1.0; /* all */
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
elog(ERROR, "unrecognized ptype: %d", (int) ptype);
|
elog(ERROR, "unrecognized ptype: %d", (int) ptype);
|
||||||
|
@ -53,6 +53,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/* yyyymmddN */
|
/* yyyymmddN */
|
||||||
#define CATALOG_VERSION_NO 202111091
|
#define CATALOG_VERSION_NO 202111171
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -102,7 +102,7 @@
|
|||||||
oprright => 'text', oprresult => 'bool', oprcom => '=(text,text)',
|
oprright => 'text', oprresult => 'bool', oprcom => '=(text,text)',
|
||||||
oprnegate => '<>(text,text)', oprcode => 'texteq', oprrest => 'eqsel',
|
oprnegate => '<>(text,text)', oprcode => 'texteq', oprrest => 'eqsel',
|
||||||
oprjoin => 'eqjoinsel' },
|
oprjoin => 'eqjoinsel' },
|
||||||
{ oid => '3877', descr => 'starts with',
|
{ oid => '3877', oid_symbol => 'TextPrefixOperator', descr => 'starts with',
|
||||||
oprname => '^@', oprleft => 'text', oprright => 'text', oprresult => 'bool',
|
oprname => '^@', oprleft => 'text', oprright => 'text', oprresult => 'bool',
|
||||||
oprcode => 'starts_with', oprrest => 'prefixsel',
|
oprcode => 'starts_with', oprrest => 'prefixsel',
|
||||||
oprjoin => 'prefixjoinsel' },
|
oprjoin => 'prefixjoinsel' },
|
||||||
|
@ -167,8 +167,12 @@
|
|||||||
proname => 'texteq', proleakproof => 't', prorettype => 'bool',
|
proname => 'texteq', proleakproof => 't', prorettype => 'bool',
|
||||||
proargtypes => 'text text', prosrc => 'texteq' },
|
proargtypes => 'text text', prosrc => 'texteq' },
|
||||||
{ oid => '3696',
|
{ oid => '3696',
|
||||||
proname => 'starts_with', proleakproof => 't', prorettype => 'bool',
|
proname => 'starts_with', prosupport => 'text_starts_with_support',
|
||||||
proargtypes => 'text text', prosrc => 'text_starts_with' },
|
proleakproof => 't', prorettype => 'bool', proargtypes => 'text text',
|
||||||
|
prosrc => 'text_starts_with' },
|
||||||
|
{ oid => '8923', descr => 'planner support for text_starts_with',
|
||||||
|
proname => 'text_starts_with_support', prorettype => 'internal',
|
||||||
|
proargtypes => 'internal', prosrc => 'text_starts_with_support' },
|
||||||
{ oid => '68',
|
{ oid => '68',
|
||||||
proname => 'xideq', proleakproof => 't', prorettype => 'bool',
|
proname => 'xideq', proleakproof => 't', prorettype => 'bool',
|
||||||
proargtypes => 'xid xid', prosrc => 'xideq' },
|
proargtypes => 'xid xid', prosrc => 'xideq' },
|
||||||
|
@ -804,6 +804,22 @@ SELECT count(*) FROM radix_text_tbl WHERE t ^@ 'Worth';
|
|||||||
2
|
2
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
|
EXPLAIN (COSTS OFF)
|
||||||
|
SELECT count(*) FROM radix_text_tbl WHERE starts_with(t, 'Worth');
|
||||||
|
QUERY PLAN
|
||||||
|
------------------------------------------------------------
|
||||||
|
Aggregate
|
||||||
|
-> Index Only Scan using sp_radix_ind on radix_text_tbl
|
||||||
|
Index Cond: (t ^@ 'Worth'::text)
|
||||||
|
Filter: starts_with(t, 'Worth'::text)
|
||||||
|
(4 rows)
|
||||||
|
|
||||||
|
SELECT count(*) FROM radix_text_tbl WHERE starts_with(t, 'Worth');
|
||||||
|
count
|
||||||
|
-------
|
||||||
|
2
|
||||||
|
(1 row)
|
||||||
|
|
||||||
-- Now check the results from bitmap indexscan
|
-- Now check the results from bitmap indexscan
|
||||||
SET enable_seqscan = OFF;
|
SET enable_seqscan = OFF;
|
||||||
SET enable_indexscan = OFF;
|
SET enable_indexscan = OFF;
|
||||||
@ -1333,6 +1349,23 @@ SELECT count(*) FROM radix_text_tbl WHERE t ^@ 'Worth';
|
|||||||
2
|
2
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
|
EXPLAIN (COSTS OFF)
|
||||||
|
SELECT count(*) FROM radix_text_tbl WHERE starts_with(t, 'Worth');
|
||||||
|
QUERY PLAN
|
||||||
|
------------------------------------------------
|
||||||
|
Aggregate
|
||||||
|
-> Bitmap Heap Scan on radix_text_tbl
|
||||||
|
Filter: starts_with(t, 'Worth'::text)
|
||||||
|
-> Bitmap Index Scan on sp_radix_ind
|
||||||
|
Index Cond: (t ^@ 'Worth'::text)
|
||||||
|
(5 rows)
|
||||||
|
|
||||||
|
SELECT count(*) FROM radix_text_tbl WHERE starts_with(t, 'Worth');
|
||||||
|
count
|
||||||
|
-------
|
||||||
|
2
|
||||||
|
(1 row)
|
||||||
|
|
||||||
RESET enable_seqscan;
|
RESET enable_seqscan;
|
||||||
RESET enable_indexscan;
|
RESET enable_indexscan;
|
||||||
RESET enable_bitmapscan;
|
RESET enable_bitmapscan;
|
||||||
|
@ -295,6 +295,10 @@ EXPLAIN (COSTS OFF)
|
|||||||
SELECT count(*) FROM radix_text_tbl WHERE t ^@ 'Worth';
|
SELECT count(*) FROM radix_text_tbl WHERE t ^@ 'Worth';
|
||||||
SELECT count(*) FROM radix_text_tbl WHERE t ^@ 'Worth';
|
SELECT count(*) FROM radix_text_tbl WHERE t ^@ 'Worth';
|
||||||
|
|
||||||
|
EXPLAIN (COSTS OFF)
|
||||||
|
SELECT count(*) FROM radix_text_tbl WHERE starts_with(t, 'Worth');
|
||||||
|
SELECT count(*) FROM radix_text_tbl WHERE starts_with(t, 'Worth');
|
||||||
|
|
||||||
-- Now check the results from bitmap indexscan
|
-- Now check the results from bitmap indexscan
|
||||||
SET enable_seqscan = OFF;
|
SET enable_seqscan = OFF;
|
||||||
SET enable_indexscan = OFF;
|
SET enable_indexscan = OFF;
|
||||||
@ -424,6 +428,10 @@ EXPLAIN (COSTS OFF)
|
|||||||
SELECT count(*) FROM radix_text_tbl WHERE t ^@ 'Worth';
|
SELECT count(*) FROM radix_text_tbl WHERE t ^@ 'Worth';
|
||||||
SELECT count(*) FROM radix_text_tbl WHERE t ^@ 'Worth';
|
SELECT count(*) FROM radix_text_tbl WHERE t ^@ 'Worth';
|
||||||
|
|
||||||
|
EXPLAIN (COSTS OFF)
|
||||||
|
SELECT count(*) FROM radix_text_tbl WHERE starts_with(t, 'Worth');
|
||||||
|
SELECT count(*) FROM radix_text_tbl WHERE starts_with(t, 'Worth');
|
||||||
|
|
||||||
RESET enable_seqscan;
|
RESET enable_seqscan;
|
||||||
RESET enable_indexscan;
|
RESET enable_indexscan;
|
||||||
RESET enable_bitmapscan;
|
RESET enable_bitmapscan;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user