Allow extensions to generate lossy index conditions.

For a long time, indxpath.c has had the ability to extract derived (lossy)
index conditions from certain operators such as LIKE.  For just as long,
it's been obvious that we really ought to make that capability available
to extensions.  This commit finally accomplishes that, by adding another
API for planner support functions that lets them create derived index
conditions for their functions.  As proof of concept, the hardwired
"special index operator" code formerly present in indxpath.c is pushed
out to planner support functions attached to LIKE and other relevant
operators.

A weak spot in this design is that an extension needs to know OIDs for
the operators, datatypes, and opfamilies involved in the transformation
it wants to make.  The core-code prototypes use hard-wired OID references
but extensions don't have that option for their own operators etc.  It's
usually possible to look up the required info, but that may be slow and
inconvenient.  However, improving that situation is a separate task.

I want to do some additional refactorization around selfuncs.c, but
that also seems like a separate task.

Discussion: https://postgr.es/m/15193.1548028093@sss.pgh.pa.us
This commit is contained in:
Tom Lane 2019-02-11 21:26:08 -05:00
parent ea92368cd1
commit 74dfe58a59
20 changed files with 1770 additions and 1290 deletions

View File

@ -3460,4 +3460,18 @@ supportfn(internal) returns internal
This can be done by a support function that implements
the <literal>SupportRequestRows</literal> request type.
</para>
<para>
For target functions that return boolean, it may be possible to
convert a function call appearing in WHERE into an indexable operator
clause or clauses. The converted clauses might be exactly equivalent
to the function's condition, or they could be somewhat weaker (that is,
they might accept some values that the function condition does not).
In the latter case the index condition is said to
be <firstterm>lossy</firstterm>; it can still be used to scan an index,
but the function call will have to be executed for each row returned by
the index to see if it really passes the WHERE condition or not.
To create such conditions, the support function must implement
the <literal>SupportRequestIndexCondition</literal> request type.
</para>
</sect1>

File diff suppressed because it is too large Load Diff

View File

@ -2067,7 +2067,8 @@ is_strict_saop(ScalarArrayOpExpr *expr, bool falseOK)
* Params and outer-level Vars, not to mention functions whose results
* may vary from one statement to the next. However, the expr's value
* will be constant over any one scan of the current query, so it can be
* used as, eg, an indexscan key.
* used as, eg, an indexscan key. (Actually, the condition for indexscan
* keys is weaker than this; see is_pseudo_constant_for_index().)
*
* CAUTION: this function omits to test for one very important class of
* not-constant expressions, namely aggregates (Aggrefs). In current usage

View File

@ -17,7 +17,8 @@ OBJS = acl.o amutils.o arrayfuncs.o array_expanded.o array_selfuncs.o \
float.o format_type.o formatting.o genfile.o \
geo_ops.o geo_selfuncs.o geo_spgist.o inet_cidr_ntop.o inet_net_pton.o \
int.o int8.o json.o jsonb.o jsonb_gin.o jsonb_op.o jsonb_util.o \
jsonfuncs.o like.o lockfuncs.o mac.o mac8.o misc.o name.o \
jsonfuncs.o like.o like_support.o lockfuncs.o \
mac.o mac8.o misc.o name.o \
network.o network_gist.o network_selfuncs.o network_spgist.o \
numeric.o numutils.o oid.o oracle_compat.o \
orderedsetaggs.o partitionfuncs.o pg_locale.o pg_lsn.o \

View File

@ -0,0 +1,313 @@
/*-------------------------------------------------------------------------
*
* like_support.c
* Planner support functions for LIKE, regex, and related operators.
*
* These routines handle special optimization of operators that can be
* used with index scans even though they are not known to the executor's
* indexscan machinery. The key idea is that these operators allow us
* to derive approximate indexscan qual clauses, such that any tuples
* that pass the operator clause itself must also satisfy the simpler
* indexscan condition(s). Then we can use the indexscan machinery
* to avoid scanning as much of the table as we'd otherwise have to,
* while applying the original operator as a qpqual condition to ensure
* we deliver only the tuples we want. (In essence, we're using a regular
* index as if it were a lossy index.)
*
* An example of what we're doing is
* textfield LIKE 'abc%def'
* from which we can generate the indexscanable conditions
* textfield >= 'abc' AND textfield < 'abd'
* which allow efficient scanning of an index on textfield.
* (In reality, character set and collation issues make the transformation
* from LIKE to indexscan limits rather harder than one might think ...
* but that's the basic idea.)
*
* Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* src/backend/utils/adt/like_support.c
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "access/stratnum.h"
#include "catalog/pg_opfamily.h"
#include "catalog/pg_type.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "nodes/supportnodes.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/pg_locale.h"
#include "utils/selfuncs.h"
static Node *like_regex_support(Node *rawreq, Pattern_Type ptype);
static List *match_pattern_prefix(Node *leftop,
Node *rightop,
Pattern_Type ptype,
Oid expr_coll,
Oid opfamily,
Oid indexcollation);
/*
* Planner support functions for LIKE, regex, and related operators
*/
Datum
textlike_support(PG_FUNCTION_ARGS)
{
Node *rawreq = (Node *) PG_GETARG_POINTER(0);
PG_RETURN_POINTER(like_regex_support(rawreq, Pattern_Type_Like));
}
Datum
texticlike_support(PG_FUNCTION_ARGS)
{
Node *rawreq = (Node *) PG_GETARG_POINTER(0);
PG_RETURN_POINTER(like_regex_support(rawreq, Pattern_Type_Like_IC));
}
Datum
textregexeq_support(PG_FUNCTION_ARGS)
{
Node *rawreq = (Node *) PG_GETARG_POINTER(0);
PG_RETURN_POINTER(like_regex_support(rawreq, Pattern_Type_Regex));
}
Datum
texticregexeq_support(PG_FUNCTION_ARGS)
{
Node *rawreq = (Node *) PG_GETARG_POINTER(0);
PG_RETURN_POINTER(like_regex_support(rawreq, Pattern_Type_Regex_IC));
}
/* Common code for the above */
static Node *
like_regex_support(Node *rawreq, Pattern_Type ptype)
{
Node *ret = NULL;
if (IsA(rawreq, SupportRequestIndexCondition))
{
/* Try to convert operator/function call to index conditions */
SupportRequestIndexCondition *req = (SupportRequestIndexCondition *) rawreq;
/*
* Currently we have no "reverse" match operators with the pattern on
* the left, so we only need consider cases with the indexkey on the
* left.
*/
if (req->indexarg != 0)
return NULL;
if (is_opclause(req->node))
{
OpExpr *clause = (OpExpr *) req->node;
Assert(list_length(clause->args) == 2);
ret = (Node *)
match_pattern_prefix((Node *) linitial(clause->args),
(Node *) lsecond(clause->args),
ptype,
clause->inputcollid,
req->opfamily,
req->indexcollation);
}
else if (is_funcclause(req->node)) /* be paranoid */
{
FuncExpr *clause = (FuncExpr *) req->node;
Assert(list_length(clause->args) == 2);
ret = (Node *)
match_pattern_prefix((Node *) linitial(clause->args),
(Node *) lsecond(clause->args),
ptype,
clause->inputcollid,
req->opfamily,
req->indexcollation);
}
}
return ret;
}
/*
* match_pattern_prefix
* Try to generate an indexqual for a LIKE or regex operator.
*/
static List *
match_pattern_prefix(Node *leftop,
Node *rightop,
Pattern_Type ptype,
Oid expr_coll,
Oid opfamily,
Oid indexcollation)
{
List *result;
Const *patt;
Const *prefix;
Pattern_Prefix_Status pstatus;
Oid ldatatype;
Oid rdatatype;
Oid oproid;
Expr *expr;
FmgrInfo ltproc;
Const *greaterstr;
/*
* Can't do anything with a non-constant or NULL pattern argument.
*
* Note that since we restrict ourselves to cases with a hard constant on
* the RHS, it's a-fortiori a pseudoconstant, and we don't need to worry
* about verifying that.
*/
if (!IsA(rightop, Const) ||
((Const *) rightop)->constisnull)
return NIL;
patt = (Const *) rightop;
/*
* Try to extract a fixed prefix from the pattern.
*/
pstatus = pattern_fixed_prefix(patt, ptype, expr_coll,
&prefix, NULL);
/* fail if no fixed prefix */
if (pstatus == Pattern_Prefix_None)
return NIL;
/*
* Must also check that index's opfamily supports the operators we will
* want to apply. (A hash index, for example, will not support ">=".)
* Currently, only btree and spgist support the operators we need.
*
* Note: actually, in the Pattern_Prefix_Exact case, we only need "=" so a
* hash index would work. Currently it doesn't seem worth checking for
* that, however.
*
* We insist on the opfamily being one of the specific ones we expect,
* else we'd do the wrong thing if someone were to make a reverse-sort
* opfamily with the same operators.
*
* The non-pattern opclasses will not sort the way we need in most non-C
* locales. We can use such an index anyway for an exact match (simple
* equality), but not for prefix-match cases. 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.
*
* While we're at it, identify the type the comparison constant(s) should
* have, based on the opfamily.
*/
switch (opfamily)
{
case TEXT_BTREE_FAM_OID:
if (!(pstatus == Pattern_Prefix_Exact ||
lc_collate_is_c(indexcollation)))
return NIL;
rdatatype = TEXTOID;
break;
case TEXT_PATTERN_BTREE_FAM_OID:
case TEXT_SPGIST_FAM_OID:
rdatatype = TEXTOID;
break;
case BPCHAR_BTREE_FAM_OID:
if (!(pstatus == Pattern_Prefix_Exact ||
lc_collate_is_c(indexcollation)))
return NIL;
rdatatype = BPCHAROID;
break;
case BPCHAR_PATTERN_BTREE_FAM_OID:
rdatatype = BPCHAROID;
break;
case BYTEA_BTREE_FAM_OID:
rdatatype = BYTEAOID;
break;
default:
return NIL;
}
/* OK, prepare to create the indexqual(s) */
ldatatype = exprType(leftop);
/*
* If necessary, coerce the prefix constant to the right type. The given
* prefix constant is either text or bytea type, therefore the only case
* where we need to do anything is when converting text to bpchar. Those
* two types are binary-compatible, so relabeling the Const node is
* sufficient.
*/
if (prefix->consttype != rdatatype)
{
Assert(prefix->consttype == TEXTOID &&
rdatatype == BPCHAROID);
prefix->consttype = rdatatype;
}
/*
* If we found an exact-match pattern, generate an "=" indexqual.
*/
if (pstatus == Pattern_Prefix_Exact)
{
oproid = get_opfamily_member(opfamily, ldatatype, rdatatype,
BTEqualStrategyNumber);
if (oproid == InvalidOid)
elog(ERROR, "no = operator for opfamily %u", opfamily);
expr = make_opclause(oproid, BOOLOID, false,
(Expr *) leftop, (Expr *) prefix,
InvalidOid, indexcollation);
result = list_make1(expr);
return result;
}
/*
* Otherwise, we have a nonempty required prefix of the values.
*
* We can always say "x >= prefix".
*/
oproid = get_opfamily_member(opfamily, ldatatype, rdatatype,
BTGreaterEqualStrategyNumber);
if (oproid == InvalidOid)
elog(ERROR, "no >= operator for opfamily %u", opfamily);
expr = make_opclause(oproid, BOOLOID, false,
(Expr *) leftop, (Expr *) prefix,
InvalidOid, indexcollation);
result = list_make1(expr);
/*-------
* If we can create a string larger than the prefix, we can say
* "x < greaterstr". NB: we rely on make_greater_string() to generate
* a guaranteed-greater string, not just a probably-greater string.
* In general this is only guaranteed in C locale, so we'd better be
* using a C-locale index collation.
*-------
*/
oproid = get_opfamily_member(opfamily, ldatatype, rdatatype,
BTLessStrategyNumber);
if (oproid == InvalidOid)
elog(ERROR, "no < operator for opfamily %u", opfamily);
fmgr_info(get_opcode(oproid), &ltproc);
greaterstr = make_greater_string(prefix, &ltproc, indexcollation);
if (greaterstr)
{
expr = make_opclause(oproid, BOOLOID, false,
(Expr *) leftop, (Expr *) greaterstr,
InvalidOid, indexcollation);
result = lappend(result, expr);
}
return result;
}

View File

@ -13,16 +13,31 @@
#include <arpa/inet.h>
#include "access/hash.h"
#include "catalog/pg_opfamily.h"
#include "catalog/pg_type.h"
#include "common/ip.h"
#include "libpq/libpq-be.h"
#include "libpq/pqformat.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "nodes/supportnodes.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/inet.h"
#include "utils/lsyscache.h"
static int32 network_cmp_internal(inet *a1, inet *a2);
static List *match_network_function(Node *leftop,
Node *rightop,
int indexarg,
Oid funcid,
Oid opfamily);
static List *match_network_subset(Node *leftop,
Node *rightop,
bool is_eq,
Oid opfamily);
static bool addressOK(unsigned char *a, int bits, int family);
static inet *internal_inetpl(inet *ip, int64 addend);
@ -574,6 +589,198 @@ network_overlap(PG_FUNCTION_ARGS)
PG_RETURN_BOOL(false);
}
/*
* Planner support function for network subset/superset operators
*/
Datum
network_subset_support(PG_FUNCTION_ARGS)
{
Node *rawreq = (Node *) PG_GETARG_POINTER(0);
Node *ret = NULL;
if (IsA(rawreq, SupportRequestIndexCondition))
{
/* Try to convert operator/function call to index conditions */
SupportRequestIndexCondition *req = (SupportRequestIndexCondition *) rawreq;
if (is_opclause(req->node))
{
OpExpr *clause = (OpExpr *) req->node;
Assert(list_length(clause->args) == 2);
ret = (Node *)
match_network_function((Node *) linitial(clause->args),
(Node *) lsecond(clause->args),
req->indexarg,
req->funcid,
req->opfamily);
}
else if (is_funcclause(req->node)) /* be paranoid */
{
FuncExpr *clause = (FuncExpr *) req->node;
Assert(list_length(clause->args) == 2);
ret = (Node *)
match_network_function((Node *) linitial(clause->args),
(Node *) lsecond(clause->args),
req->indexarg,
req->funcid,
req->opfamily);
}
}
PG_RETURN_POINTER(ret);
}
/*
* match_network_function
* Try to generate an indexqual for a network subset/superset function.
*
* This layer is just concerned with identifying the function and swapping
* the arguments if necessary.
*/
static List *
match_network_function(Node *leftop,
Node *rightop,
int indexarg,
Oid funcid,
Oid opfamily)
{
switch (funcid)
{
case F_NETWORK_SUB:
/* indexkey must be on the left */
if (indexarg != 0)
return NIL;
return match_network_subset(leftop, rightop, false, opfamily);
case F_NETWORK_SUBEQ:
/* indexkey must be on the left */
if (indexarg != 0)
return NIL;
return match_network_subset(leftop, rightop, true, opfamily);
case F_NETWORK_SUP:
/* indexkey must be on the right */
if (indexarg != 1)
return NIL;
return match_network_subset(rightop, leftop, false, opfamily);
case F_NETWORK_SUPEQ:
/* indexkey must be on the right */
if (indexarg != 1)
return NIL;
return match_network_subset(rightop, leftop, true, opfamily);
default:
/*
* We'd only get here if somebody attached this support function
* to an unexpected function. Maybe we should complain, but for
* now, do nothing.
*/
return NIL;
}
}
/*
* match_network_subset
* Try to generate an indexqual for a network subset function.
*/
static List *
match_network_subset(Node *leftop,
Node *rightop,
bool is_eq,
Oid opfamily)
{
List *result;
Datum rightopval;
Oid datatype = INETOID;
Oid opr1oid;
Oid opr2oid;
Datum opr1right;
Datum opr2right;
Expr *expr;
/*
* Can't do anything with a non-constant or NULL comparison value.
*
* Note that since we restrict ourselves to cases with a hard constant on
* the RHS, it's a-fortiori a pseudoconstant, and we don't need to worry
* about verifying that.
*/
if (!IsA(rightop, Const) ||
((Const *) rightop)->constisnull)
return NIL;
rightopval = ((Const *) rightop)->constvalue;
/*
* Must check that index's opfamily supports the operators we will want to
* apply.
*
* We insist on the opfamily being the specific one we expect, else we'd
* do the wrong thing if someone were to make a reverse-sort opfamily with
* the same operators.
*/
if (opfamily != NETWORK_BTREE_FAM_OID)
return NIL;
/*
* create clause "key >= network_scan_first( rightopval )", or ">" if the
* operator disallows equality.
*
* Note: seeing that this function supports only fixed values for opfamily
* and datatype, we could just hard-wire the operator OIDs instead of
* looking them up. But for now it seems better to be general.
*/
if (is_eq)
{
opr1oid = get_opfamily_member(opfamily, datatype, datatype,
BTGreaterEqualStrategyNumber);
if (opr1oid == InvalidOid)
elog(ERROR, "no >= operator for opfamily %u", opfamily);
}
else
{
opr1oid = get_opfamily_member(opfamily, datatype, datatype,
BTGreaterStrategyNumber);
if (opr1oid == InvalidOid)
elog(ERROR, "no > operator for opfamily %u", opfamily);
}
opr1right = network_scan_first(rightopval);
expr = make_opclause(opr1oid, BOOLOID, false,
(Expr *) leftop,
(Expr *) makeConst(datatype, -1,
InvalidOid, /* not collatable */
-1, opr1right,
false, false),
InvalidOid, InvalidOid);
result = list_make1(expr);
/* create clause "key <= network_scan_last( rightopval )" */
opr2oid = get_opfamily_member(opfamily, datatype, datatype,
BTLessEqualStrategyNumber);
if (opr2oid == InvalidOid)
elog(ERROR, "no <= operator for opfamily %u", opfamily);
opr2right = network_scan_last(rightopval);
expr = make_opclause(opr2oid, BOOLOID, false,
(Expr *) leftop,
(Expr *) makeConst(datatype, -1,
InvalidOid, /* not collatable */
-1, opr2right,
false, false),
InvalidOid, InvalidOid);
result = lappend(result, expr);
return result;
}
/*
* Extract data from a network datatype.
*/

View File

@ -53,6 +53,6 @@
*/
/* yyyymmddN */
#define CATALOG_VERSION_NO 201902111
#define CATALOG_VERSION_NO 201902112
#endif

View File

@ -189,17 +189,21 @@
prosrc => 'i4tochar' },
{ oid => '79',
proname => 'nameregexeq', prorettype => 'bool', proargtypes => 'name text',
prosrc => 'nameregexeq' },
proname => 'nameregexeq', prosupport => 'textregexeq_support',
prorettype => 'bool', proargtypes => 'name text', prosrc => 'nameregexeq' },
{ oid => '1252',
proname => 'nameregexne', prorettype => 'bool', proargtypes => 'name text',
prosrc => 'nameregexne' },
{ oid => '1254',
proname => 'textregexeq', prorettype => 'bool', proargtypes => 'text text',
prosrc => 'textregexeq' },
proname => 'textregexeq', prosupport => 'textregexeq_support',
prorettype => 'bool', proargtypes => 'text text', prosrc => 'textregexeq' },
{ oid => '1256',
proname => 'textregexne', prorettype => 'bool', proargtypes => 'text text',
prosrc => 'textregexne' },
{ oid => '1364', descr => 'planner support for textregexeq',
proname => 'textregexeq_support', prorettype => 'internal',
proargtypes => 'internal', prosrc => 'textregexeq_support' },
{ oid => '1257', descr => 'length',
proname => 'textlen', prorettype => 'int4', proargtypes => 'text',
prosrc => 'textlen' },
@ -1637,8 +1641,11 @@
proname => 'position', prorettype => 'int4', proargtypes => 'text text',
prosrc => 'textpos' },
{ oid => '850',
proname => 'textlike', prorettype => 'bool', proargtypes => 'text text',
prosrc => 'textlike' },
proname => 'textlike', prosupport => 'textlike_support', prorettype => 'bool',
proargtypes => 'text text', prosrc => 'textlike' },
{ oid => '1023', descr => 'planner support for textlike',
proname => 'textlike_support', prorettype => 'internal',
proargtypes => 'internal', prosrc => 'textlike_support' },
{ oid => '851',
proname => 'textnlike', prorettype => 'bool', proargtypes => 'text text',
prosrc => 'textnlike' },
@ -1663,8 +1670,8 @@
proargtypes => 'int4 int8', prosrc => 'int48ge' },
{ oid => '858',
proname => 'namelike', prorettype => 'bool', proargtypes => 'name text',
prosrc => 'namelike' },
proname => 'namelike', prosupport => 'textlike_support', prorettype => 'bool',
proargtypes => 'name text', prosrc => 'namelike' },
{ oid => '859',
proname => 'namenlike', prorettype => 'bool', proargtypes => 'name text',
prosrc => 'namenlike' },
@ -2354,14 +2361,17 @@
prosrc => 'int8smaller' },
{ oid => '1238',
proname => 'texticregexeq', prorettype => 'bool', proargtypes => 'text text',
prosrc => 'texticregexeq' },
proname => 'texticregexeq', prosupport => 'texticregexeq_support',
prorettype => 'bool', proargtypes => 'text text', prosrc => 'texticregexeq' },
{ oid => '1024', descr => 'planner support for texticregexeq',
proname => 'texticregexeq_support', prorettype => 'internal',
proargtypes => 'internal', prosrc => 'texticregexeq_support' },
{ oid => '1239',
proname => 'texticregexne', prorettype => 'bool', proargtypes => 'text text',
prosrc => 'texticregexne' },
{ oid => '1240',
proname => 'nameicregexeq', prorettype => 'bool', proargtypes => 'name text',
prosrc => 'nameicregexeq' },
proname => 'nameicregexeq', prosupport => 'texticregexeq_support',
prorettype => 'bool', proargtypes => 'name text', prosrc => 'nameicregexeq' },
{ oid => '1241',
proname => 'nameicregexne', prorettype => 'bool', proargtypes => 'name text',
prosrc => 'nameicregexne' },
@ -3130,14 +3140,14 @@
prosrc => 'bittypmodout' },
{ oid => '1569', descr => 'matches LIKE expression',
proname => 'like', prorettype => 'bool', proargtypes => 'text text',
prosrc => 'textlike' },
proname => 'like', prosupport => 'textlike_support', prorettype => 'bool',
proargtypes => 'text text', prosrc => 'textlike' },
{ oid => '1570', descr => 'does not match LIKE expression',
proname => 'notlike', prorettype => 'bool', proargtypes => 'text text',
prosrc => 'textnlike' },
{ oid => '1571', descr => 'matches LIKE expression',
proname => 'like', prorettype => 'bool', proargtypes => 'name text',
prosrc => 'namelike' },
proname => 'like', prosupport => 'textlike_support', prorettype => 'bool',
proargtypes => 'name text', prosrc => 'namelike' },
{ oid => '1572', descr => 'does not match LIKE expression',
proname => 'notlike', prorettype => 'bool', proargtypes => 'name text',
prosrc => 'namenlike' },
@ -3301,21 +3311,24 @@
proargtypes => 'float8 interval', prosrc => 'mul_d_interval' },
{ oid => '1631',
proname => 'bpcharlike', prorettype => 'bool', proargtypes => 'bpchar text',
prosrc => 'textlike' },
proname => 'bpcharlike', prosupport => 'textlike_support',
prorettype => 'bool', proargtypes => 'bpchar text', prosrc => 'textlike' },
{ oid => '1632',
proname => 'bpcharnlike', prorettype => 'bool', proargtypes => 'bpchar text',
prosrc => 'textnlike' },
{ oid => '1633',
proname => 'texticlike', prorettype => 'bool', proargtypes => 'text text',
prosrc => 'texticlike' },
proname => 'texticlike', prosupport => 'texticlike_support',
prorettype => 'bool', proargtypes => 'text text', prosrc => 'texticlike' },
{ oid => '1025', descr => 'planner support for texticlike',
proname => 'texticlike_support', prorettype => 'internal',
proargtypes => 'internal', prosrc => 'texticlike_support' },
{ oid => '1634',
proname => 'texticnlike', prorettype => 'bool', proargtypes => 'text text',
prosrc => 'texticnlike' },
{ oid => '1635',
proname => 'nameiclike', prorettype => 'bool', proargtypes => 'name text',
prosrc => 'nameiclike' },
proname => 'nameiclike', prosupport => 'texticlike_support',
prorettype => 'bool', proargtypes => 'name text', prosrc => 'nameiclike' },
{ oid => '1636',
proname => 'nameicnlike', prorettype => 'bool', proargtypes => 'name text',
prosrc => 'nameicnlike' },
@ -3324,20 +3337,21 @@
prosrc => 'like_escape' },
{ oid => '1656',
proname => 'bpcharicregexeq', prorettype => 'bool',
proargtypes => 'bpchar text', prosrc => 'texticregexeq' },
proname => 'bpcharicregexeq', prosupport => 'texticregexeq_support',
prorettype => 'bool', proargtypes => 'bpchar text',
prosrc => 'texticregexeq' },
{ oid => '1657',
proname => 'bpcharicregexne', prorettype => 'bool',
proargtypes => 'bpchar text', prosrc => 'texticregexne' },
{ oid => '1658',
proname => 'bpcharregexeq', prorettype => 'bool',
proargtypes => 'bpchar text', prosrc => 'textregexeq' },
proname => 'bpcharregexeq', prosupport => 'textregexeq_support',
prorettype => 'bool', proargtypes => 'bpchar text', prosrc => 'textregexeq' },
{ oid => '1659',
proname => 'bpcharregexne', prorettype => 'bool',
proargtypes => 'bpchar text', prosrc => 'textregexne' },
{ oid => '1660',
proname => 'bpchariclike', prorettype => 'bool', proargtypes => 'bpchar text',
prosrc => 'texticlike' },
proname => 'bpchariclike', prosupport => 'texticlike_support',
prorettype => 'bool', proargtypes => 'bpchar text', prosrc => 'texticlike' },
{ oid => '1661',
proname => 'bpcharicnlike', prorettype => 'bool',
proargtypes => 'bpchar text', prosrc => 'texticnlike' },
@ -3878,17 +3892,21 @@
proname => 'network_cmp', proleakproof => 't', prorettype => 'int4',
proargtypes => 'inet inet', prosrc => 'network_cmp' },
{ oid => '927',
proname => 'network_sub', prorettype => 'bool', proargtypes => 'inet inet',
prosrc => 'network_sub' },
proname => 'network_sub', prosupport => 'network_subset_support',
prorettype => 'bool', proargtypes => 'inet inet', prosrc => 'network_sub' },
{ oid => '928',
proname => 'network_subeq', prorettype => 'bool', proargtypes => 'inet inet',
prosrc => 'network_subeq' },
proname => 'network_subeq', prosupport => 'network_subset_support',
prorettype => 'bool', proargtypes => 'inet inet', prosrc => 'network_subeq' },
{ oid => '929',
proname => 'network_sup', prorettype => 'bool', proargtypes => 'inet inet',
prosrc => 'network_sup' },
proname => 'network_sup', prosupport => 'network_subset_support',
prorettype => 'bool', proargtypes => 'inet inet', prosrc => 'network_sup' },
{ oid => '930',
proname => 'network_supeq', prorettype => 'bool', proargtypes => 'inet inet',
prosrc => 'network_supeq' },
proname => 'network_supeq', prosupport => 'network_subset_support',
prorettype => 'bool', proargtypes => 'inet inet', prosrc => 'network_supeq' },
{ oid => '1173', descr => 'planner support for network_sub/superset',
proname => 'network_subset_support', prorettype => 'internal',
proargtypes => 'internal', prosrc => 'network_subset_support' },
{ oid => '3551',
proname => 'network_overlap', prorettype => 'bool',
proargtypes => 'inet inet', prosrc => 'network_overlap' },
@ -5482,14 +5500,14 @@
prosrc => 'select $1::pg_catalog.text || $2' },
{ oid => '2005',
proname => 'bytealike', prorettype => 'bool', proargtypes => 'bytea bytea',
prosrc => 'bytealike' },
proname => 'bytealike', prosupport => 'textlike_support',
prorettype => 'bool', proargtypes => 'bytea bytea', prosrc => 'bytealike' },
{ oid => '2006',
proname => 'byteanlike', prorettype => 'bool', proargtypes => 'bytea bytea',
prosrc => 'byteanlike' },
{ oid => '2007', descr => 'matches LIKE expression',
proname => 'like', prorettype => 'bool', proargtypes => 'bytea bytea',
prosrc => 'bytealike' },
proname => 'like', prosupport => 'textlike_support', prorettype => 'bool',
proargtypes => 'bytea bytea', prosrc => 'bytealike' },
{ oid => '2008', descr => 'does not match LIKE expression',
proname => 'notlike', prorettype => 'bool', proargtypes => 'bytea bytea',
prosrc => 'byteanlike' },

View File

@ -510,7 +510,8 @@ typedef enum NodeTag
T_SupportRequestSimplify, /* in nodes/supportnodes.h */
T_SupportRequestSelectivity, /* in nodes/supportnodes.h */
T_SupportRequestCost, /* in nodes/supportnodes.h */
T_SupportRequestRows /* in nodes/supportnodes.h */
T_SupportRequestRows, /* in nodes/supportnodes.h */
T_SupportRequestIndexCondition /* in nodes/supportnodes.h */
} NodeTag;
/*

View File

@ -764,7 +764,12 @@ typedef struct RelOptInfo
* (by plancat.c), indrestrictinfo and predOK are set later, in
* check_index_predicates().
*/
typedef struct IndexOptInfo
#ifndef HAVE_INDEXOPTINFO_TYPEDEF
typedef struct IndexOptInfo IndexOptInfo;
#define HAVE_INDEXOPTINFO_TYPEDEF 1
#endif
struct IndexOptInfo
{
NodeTag type;
@ -818,7 +823,7 @@ typedef struct IndexOptInfo
bool amcanparallel; /* does AM support parallel scan? */
/* Rather than include amapi.h here, we declare amcostestimate like this */
void (*amcostestimate) (); /* AM's cost estimator */
} IndexOptInfo;
};
/*
* ForeignKeyOptInfo

View File

@ -35,7 +35,8 @@
#include "nodes/primnodes.h"
struct PlannerInfo; /* avoid including relation.h here */
struct PlannerInfo; /* avoid including pathnodes.h here */
struct IndexOptInfo;
struct SpecialJoinInfo;
@ -167,4 +168,75 @@ typedef struct SupportRequestRows
double rows; /* number of rows expected to be returned */
} SupportRequestRows;
/*
* The IndexCondition request allows the support function to generate
* a directly-indexable condition based on a target function call that is
* not itself indexable. The target function call must appear at the top
* level of WHERE or JOIN/ON, so this applies only to functions returning
* boolean.
*
* The "node" argument is the parse node that is invoking the target function;
* currently this will always be a FuncExpr or OpExpr. The call is made
* only if at least one function argument matches an index column's variable
* or expression. "indexarg" identifies the matching argument (it's the
* argument's zero-based index in the node's args list).
*
* If the transformation is possible, return a List of directly-indexable
* condition expressions, else return NULL. (A List is used because it's
* sometimes useful to generate more than one indexable condition, such as
* when a LIKE with constant prefix gives rise to both >= and < conditions.)
*
* "Directly indexable" means that the condition must be directly executable
* by the index machinery. Typically this means that it is a binary OpExpr
* with the index column value on the left, a pseudo-constant on the right,
* and an operator that is in the index column's operator family. Other
* possibilities include RowCompareExpr, ScalarArrayOpExpr, and NullTest,
* depending on the index type; but those seem less likely to be useful for
* derived index conditions. "Pseudo-constant" means that the right-hand
* expression must not contain any volatile functions, nor any Vars of the
* table the index is for; use is_pseudo_constant_for_index() to check this.
* (Note: if the passed "node" is an OpExpr, the core planner already verified
* that the non-indexkey operand is pseudo-constant; but when the "node"
* is a FuncExpr, it does not check, since it doesn't know which of the
* function's arguments you might need to use in an index comparison value.)
*
* In many cases, an index condition can be generated but it is weaker than
* the function condition itself; for example, a LIKE with a constant prefix
* can produce an index range check based on the prefix, but we still need
* to execute the LIKE operator to verify the rest of the pattern. We say
* that such an index condition is "lossy". When returning an index condition,
* you should set the "lossy" request field to true if the condition is lossy,
* or false if it is an exact equivalent of the function's result. The core
* code will initialize that field to true, which is the common case.
*
* It is important to verify that the index operator family is the correct
* one for the condition you want to generate. Core support functions tend
* to use the known OID of a built-in opfamily for this, but extensions need
* to work harder, since their OIDs aren't fixed. A possibly workable
* answer for an index on an extension datatype is to verify the index AM's
* OID instead, and then assume that there's only one relevant opclass for
* your datatype so the opfamily must be the right one. Generating OpExpr
* nodes may also require knowing extension datatype OIDs (often you can
* find these out by applying exprType() to a function argument) and
* operator OIDs (which you can look up using get_opfamily_member).
*/
typedef struct SupportRequestIndexCondition
{
NodeTag type;
/* Input fields: */
struct PlannerInfo *root; /* Planner's infrastructure */
Oid funcid; /* function we are inquiring about */
Node *node; /* parse node invoking function */
int indexarg; /* index of function arg matching indexcol */
struct IndexOptInfo *index; /* planner's info about target index */
int indexcol; /* index of target index column (0-based) */
Oid opfamily; /* index column's operator family */
Oid indexcollation; /* index column's collation */
/* Output fields: */
bool lossy; /* set to false if index condition is an exact
* equivalent of the function call */
} SupportRequestIndexCondition;
#endif /* SUPPORTNODES_H */

View File

@ -35,7 +35,11 @@ typedef struct PlannerInfo PlannerInfo;
#define HAVE_PLANNERINFO_TYPEDEF 1
#endif
/* Likewise for SpecialJoinInfo. */
/* Likewise for IndexOptInfo and SpecialJoinInfo. */
#ifndef HAVE_INDEXOPTINFO_TYPEDEF
typedef struct IndexOptInfo IndexOptInfo;
#define HAVE_INDEXOPTINFO_TYPEDEF 1
#endif
#ifndef HAVE_SPECIALJOININFO_TYPEDEF
typedef struct SpecialJoinInfo SpecialJoinInfo;
#define HAVE_SPECIALJOININFO_TYPEDEF 1
@ -74,6 +78,10 @@ extern PGDLLIMPORT int effective_cache_size;
extern double clamp_row_est(double nrows);
/* in path/indxpath.c: */
extern bool is_pseudo_constant_for_index(Node *expr, IndexOptInfo *index);
/* in plan/planner.c: */
/* possible values for force_parallel_mode */

View File

@ -105,6 +105,15 @@ SELECT b.*
set enable_seqscan to false;
set enable_indexscan to true;
set enable_bitmapscan to false;
explain (costs off)
select proname from pg_proc where proname like E'RI\\_FKey%del' order by 1;
QUERY PLAN
------------------------------------------------------------------------------
Index Only Scan using pg_proc_proname_args_nsp_index on pg_proc
Index Cond: ((proname >= 'RI_FKey'::text) AND (proname < 'RI_FKez'::text))
Filter: (proname ~~ 'RI\_FKey%del'::text)
(3 rows)
select proname from pg_proc where proname like E'RI\\_FKey%del' order by 1;
proname
------------------------
@ -115,8 +124,42 @@ select proname from pg_proc where proname like E'RI\\_FKey%del' order by 1;
RI_FKey_setnull_del
(5 rows)
explain (costs off)
select proname from pg_proc where proname ilike '00%foo' order by 1;
QUERY PLAN
--------------------------------------------------------------------
Index Only Scan using pg_proc_proname_args_nsp_index on pg_proc
Index Cond: ((proname >= '00'::text) AND (proname < '01'::text))
Filter: (proname ~~* '00%foo'::text)
(3 rows)
select proname from pg_proc where proname ilike '00%foo' order by 1;
proname
---------
(0 rows)
explain (costs off)
select proname from pg_proc where proname ilike 'ri%foo' order by 1;
QUERY PLAN
-----------------------------------------------------------------
Index Only Scan using pg_proc_proname_args_nsp_index on pg_proc
Filter: (proname ~~* 'ri%foo'::text)
(2 rows)
set enable_indexscan to false;
set enable_bitmapscan to true;
explain (costs off)
select proname from pg_proc where proname like E'RI\\_FKey%del' order by 1;
QUERY PLAN
------------------------------------------------------------------------------------------
Sort
Sort Key: proname
-> Bitmap Heap Scan on pg_proc
Filter: (proname ~~ 'RI\_FKey%del'::text)
-> Bitmap Index Scan on pg_proc_proname_args_nsp_index
Index Cond: ((proname >= 'RI_FKey'::text) AND (proname < 'RI_FKez'::text))
(6 rows)
select proname from pg_proc where proname like E'RI\\_FKey%del' order by 1;
proname
------------------------
@ -127,6 +170,34 @@ select proname from pg_proc where proname like E'RI\\_FKey%del' order by 1;
RI_FKey_setnull_del
(5 rows)
explain (costs off)
select proname from pg_proc where proname ilike '00%foo' order by 1;
QUERY PLAN
--------------------------------------------------------------------------------
Sort
Sort Key: proname
-> Bitmap Heap Scan on pg_proc
Filter: (proname ~~* '00%foo'::text)
-> Bitmap Index Scan on pg_proc_proname_args_nsp_index
Index Cond: ((proname >= '00'::text) AND (proname < '01'::text))
(6 rows)
select proname from pg_proc where proname ilike '00%foo' order by 1;
proname
---------
(0 rows)
explain (costs off)
select proname from pg_proc where proname ilike 'ri%foo' order by 1;
QUERY PLAN
-----------------------------------------------------------------
Index Only Scan using pg_proc_proname_args_nsp_index on pg_proc
Filter: (proname ~~* 'ri%foo'::text)
(2 rows)
reset enable_seqscan;
reset enable_indexscan;
reset enable_bitmapscan;
--
-- Test B-tree page deletion. In particular, deleting a non-leaf page.
--

View File

@ -3201,6 +3201,24 @@ explain (costs off)
Index Cond: (b = false)
(3 rows)
explain (costs off)
select * from boolindex where b is true order by i desc limit 10;
QUERY PLAN
----------------------------------------------------------------
Limit
-> Index Scan Backward using boolindex_b_i_key on boolindex
Index Cond: (b = true)
(3 rows)
explain (costs off)
select * from boolindex where b is false order by i desc limit 10;
QUERY PLAN
----------------------------------------------------------------
Limit
-> Index Scan Backward using boolindex_b_i_key on boolindex
Index Cond: (b = false)
(3 rows)
--
-- Test for multilevel page deletion
--

View File

@ -242,6 +242,15 @@ SELECT '' AS ten, set_masklen(inet(text(i)), 24) FROM INET_TBL;
-- check that btree index works correctly
CREATE INDEX inet_idx1 ON inet_tbl(i);
SET enable_seqscan TO off;
EXPLAIN (COSTS OFF)
SELECT * FROM inet_tbl WHERE i<<'192.168.1.0/24'::cidr;
QUERY PLAN
-------------------------------------------------------------------------------
Index Scan using inet_idx1 on inet_tbl
Index Cond: ((i > '192.168.1.0/24'::inet) AND (i <= '192.168.1.255'::inet))
Filter: (i << '192.168.1.0/24'::inet)
(3 rows)
SELECT * FROM inet_tbl WHERE i<<'192.168.1.0/24'::cidr;
c | i
----------------+------------------
@ -250,6 +259,15 @@ SELECT * FROM inet_tbl WHERE i<<'192.168.1.0/24'::cidr;
192.168.1.0/26 | 192.168.1.226
(3 rows)
EXPLAIN (COSTS OFF)
SELECT * FROM inet_tbl WHERE i<<='192.168.1.0/24'::cidr;
QUERY PLAN
--------------------------------------------------------------------------------
Index Scan using inet_idx1 on inet_tbl
Index Cond: ((i >= '192.168.1.0/24'::inet) AND (i <= '192.168.1.255'::inet))
Filter: (i <<= '192.168.1.0/24'::inet)
(3 rows)
SELECT * FROM inet_tbl WHERE i<<='192.168.1.0/24'::cidr;
c | i
----------------+------------------
@ -261,6 +279,43 @@ SELECT * FROM inet_tbl WHERE i<<='192.168.1.0/24'::cidr;
192.168.1.0/26 | 192.168.1.226
(6 rows)
EXPLAIN (COSTS OFF)
SELECT * FROM inet_tbl WHERE '192.168.1.0/24'::cidr >>= i;
QUERY PLAN
--------------------------------------------------------------------------------
Index Scan using inet_idx1 on inet_tbl
Index Cond: ((i >= '192.168.1.0/24'::inet) AND (i <= '192.168.1.255'::inet))
Filter: ('192.168.1.0/24'::inet >>= i)
(3 rows)
SELECT * FROM inet_tbl WHERE '192.168.1.0/24'::cidr >>= i;
c | i
----------------+------------------
192.168.1.0/24 | 192.168.1.0/24
192.168.1.0/24 | 192.168.1.226/24
192.168.1.0/24 | 192.168.1.255/24
192.168.1.0/24 | 192.168.1.0/25
192.168.1.0/24 | 192.168.1.255/25
192.168.1.0/26 | 192.168.1.226
(6 rows)
EXPLAIN (COSTS OFF)
SELECT * FROM inet_tbl WHERE '192.168.1.0/24'::cidr >> i;
QUERY PLAN
-------------------------------------------------------------------------------
Index Scan using inet_idx1 on inet_tbl
Index Cond: ((i > '192.168.1.0/24'::inet) AND (i <= '192.168.1.255'::inet))
Filter: ('192.168.1.0/24'::inet >> i)
(3 rows)
SELECT * FROM inet_tbl WHERE '192.168.1.0/24'::cidr >> i;
c | i
----------------+------------------
192.168.1.0/24 | 192.168.1.0/25
192.168.1.0/24 | 192.168.1.255/25
192.168.1.0/26 | 192.168.1.226
(3 rows)
SET enable_seqscan TO on;
DROP INDEX inet_idx1;
-- check that gist index works correctly

View File

@ -294,6 +294,105 @@ order by thousand, tenthous;
999 | 9999
(25 rows)
explain (costs off)
select thousand, tenthous, four from tenk1
where (thousand, tenthous, four) > (998, 5000, 3)
order by thousand, tenthous;
QUERY PLAN
-----------------------------------------------------------------------
Sort
Sort Key: thousand, tenthous
-> Bitmap Heap Scan on tenk1
Filter: (ROW(thousand, tenthous, four) > ROW(998, 5000, 3))
-> Bitmap Index Scan on tenk1_thous_tenthous
Index Cond: (ROW(thousand, tenthous) >= ROW(998, 5000))
(6 rows)
select thousand, tenthous, four from tenk1
where (thousand, tenthous, four) > (998, 5000, 3)
order by thousand, tenthous;
thousand | tenthous | four
----------+----------+------
998 | 5998 | 2
998 | 6998 | 2
998 | 7998 | 2
998 | 8998 | 2
998 | 9998 | 2
999 | 999 | 3
999 | 1999 | 3
999 | 2999 | 3
999 | 3999 | 3
999 | 4999 | 3
999 | 5999 | 3
999 | 6999 | 3
999 | 7999 | 3
999 | 8999 | 3
999 | 9999 | 3
(15 rows)
explain (costs off)
select thousand, tenthous from tenk1
where (998, 5000) < (thousand, tenthous)
order by thousand, tenthous;
QUERY PLAN
----------------------------------------------------------
Index Only Scan using tenk1_thous_tenthous on tenk1
Index Cond: (ROW(thousand, tenthous) > ROW(998, 5000))
(2 rows)
select thousand, tenthous from tenk1
where (998, 5000) < (thousand, tenthous)
order by thousand, tenthous;
thousand | tenthous
----------+----------
998 | 5998
998 | 6998
998 | 7998
998 | 8998
998 | 9998
999 | 999
999 | 1999
999 | 2999
999 | 3999
999 | 4999
999 | 5999
999 | 6999
999 | 7999
999 | 8999
999 | 9999
(15 rows)
explain (costs off)
select thousand, hundred from tenk1
where (998, 5000) < (thousand, hundred)
order by thousand, hundred;
QUERY PLAN
-----------------------------------------------------------
Sort
Sort Key: thousand, hundred
-> Bitmap Heap Scan on tenk1
Filter: (ROW(998, 5000) < ROW(thousand, hundred))
-> Bitmap Index Scan on tenk1_thous_tenthous
Index Cond: (thousand >= 998)
(6 rows)
select thousand, hundred from tenk1
where (998, 5000) < (thousand, hundred)
order by thousand, hundred;
thousand | hundred
----------+---------
999 | 99
999 | 99
999 | 99
999 | 99
999 | 99
999 | 99
999 | 99
999 | 99
999 | 99
999 | 99
(10 rows)
-- Test case for bug #14010: indexed row comparisons fail with nulls
create temp table test_table (a text, b text);
insert into test_table values ('a', 'b');

View File

@ -59,11 +59,29 @@ SELECT b.*
set enable_seqscan to false;
set enable_indexscan to true;
set enable_bitmapscan to false;
explain (costs off)
select proname from pg_proc where proname like E'RI\\_FKey%del' order by 1;
select proname from pg_proc where proname like E'RI\\_FKey%del' order by 1;
explain (costs off)
select proname from pg_proc where proname ilike '00%foo' order by 1;
select proname from pg_proc where proname ilike '00%foo' order by 1;
explain (costs off)
select proname from pg_proc where proname ilike 'ri%foo' order by 1;
set enable_indexscan to false;
set enable_bitmapscan to true;
explain (costs off)
select proname from pg_proc where proname like E'RI\\_FKey%del' order by 1;
select proname from pg_proc where proname like E'RI\\_FKey%del' order by 1;
explain (costs off)
select proname from pg_proc where proname ilike '00%foo' order by 1;
select proname from pg_proc where proname ilike '00%foo' order by 1;
explain (costs off)
select proname from pg_proc where proname ilike 'ri%foo' order by 1;
reset enable_seqscan;
reset enable_indexscan;
reset enable_bitmapscan;
--
-- Test B-tree page deletion. In particular, deleting a non-leaf page.

View File

@ -1135,6 +1135,10 @@ explain (costs off)
select * from boolindex where b = true order by i desc limit 10;
explain (costs off)
select * from boolindex where not b order by i limit 10;
explain (costs off)
select * from boolindex where b is true order by i desc limit 10;
explain (costs off)
select * from boolindex where b is false order by i desc limit 10;
--
-- Test for multilevel page deletion

View File

@ -65,8 +65,18 @@ SELECT '' AS ten, set_masklen(inet(text(i)), 24) FROM INET_TBL;
-- check that btree index works correctly
CREATE INDEX inet_idx1 ON inet_tbl(i);
SET enable_seqscan TO off;
EXPLAIN (COSTS OFF)
SELECT * FROM inet_tbl WHERE i<<'192.168.1.0/24'::cidr;
SELECT * FROM inet_tbl WHERE i<<'192.168.1.0/24'::cidr;
EXPLAIN (COSTS OFF)
SELECT * FROM inet_tbl WHERE i<<='192.168.1.0/24'::cidr;
SELECT * FROM inet_tbl WHERE i<<='192.168.1.0/24'::cidr;
EXPLAIN (COSTS OFF)
SELECT * FROM inet_tbl WHERE '192.168.1.0/24'::cidr >>= i;
SELECT * FROM inet_tbl WHERE '192.168.1.0/24'::cidr >>= i;
EXPLAIN (COSTS OFF)
SELECT * FROM inet_tbl WHERE '192.168.1.0/24'::cidr >> i;
SELECT * FROM inet_tbl WHERE '192.168.1.0/24'::cidr >> i;
SET enable_seqscan TO on;
DROP INDEX inet_idx1;

View File

@ -119,6 +119,33 @@ select thousand, tenthous from tenk1
where (thousand, tenthous) >= (997, 5000)
order by thousand, tenthous;
explain (costs off)
select thousand, tenthous, four from tenk1
where (thousand, tenthous, four) > (998, 5000, 3)
order by thousand, tenthous;
select thousand, tenthous, four from tenk1
where (thousand, tenthous, four) > (998, 5000, 3)
order by thousand, tenthous;
explain (costs off)
select thousand, tenthous from tenk1
where (998, 5000) < (thousand, tenthous)
order by thousand, tenthous;
select thousand, tenthous from tenk1
where (998, 5000) < (thousand, tenthous)
order by thousand, tenthous;
explain (costs off)
select thousand, hundred from tenk1
where (998, 5000) < (thousand, hundred)
order by thousand, hundred;
select thousand, hundred from tenk1
where (998, 5000) < (thousand, hundred)
order by thousand, hundred;
-- Test case for bug #14010: indexed row comparisons fail with nulls
create temp table test_table (a text, b text);
insert into test_table values ('a', 'b');