Skip to content

Commit 075df6b

Browse files
committed
Add planner support functions for range operators <@ and @>.
These support functions will transform expressions with constant range values into direct comparisons on the range bound values, which are frequently better-optimizable. The transformation is skipped however if it would require double evaluation of a volatile or expensive element expression. Along the way, add the range opfamily OID to range typcache entries, since load_rangetype_info has to compute that anyway and it seems silly to duplicate the work later. Kim Johan Andersson and Jian He, reviewed by Laurenz Albe Discussion: https://postgr.es/m/94f64d1f-b8c0-b0c5-98bc-0793a34e0851@kimmet.dk
1 parent abb0b4f commit 075df6b

File tree

8 files changed

+469
-11
lines changed

8 files changed

+469
-11
lines changed

src/backend/utils/adt/rangetypes.c

Lines changed: 240 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,19 +30,20 @@
3030
*/
3131
#include "postgres.h"
3232

33-
#include "access/tupmacs.h"
3433
#include "common/hashfn.h"
35-
#include "lib/stringinfo.h"
3634
#include "libpq/pqformat.h"
3735
#include "miscadmin.h"
36+
#include "nodes/makefuncs.h"
3837
#include "nodes/miscnodes.h"
39-
#include "port/pg_bitutils.h"
38+
#include "nodes/supportnodes.h"
39+
#include "optimizer/clauses.h"
40+
#include "optimizer/cost.h"
41+
#include "optimizer/optimizer.h"
4042
#include "utils/builtins.h"
4143
#include "utils/date.h"
4244
#include "utils/lsyscache.h"
4345
#include "utils/rangetypes.h"
4446
#include "utils/timestamp.h"
45-
#include "varatt.h"
4647

4748

4849
/* fn_extra cache entry for one of the range I/O functions */
@@ -69,6 +70,12 @@ static Size datum_compute_size(Size data_length, Datum val, bool typbyval,
6970
char typalign, int16 typlen, char typstorage);
7071
static Pointer datum_write(Pointer ptr, Datum datum, bool typbyval,
7172
char typalign, int16 typlen, char typstorage);
73+
static Node *find_simplified_clause(PlannerInfo *root,
74+
Expr *rangeExpr, Expr *elemExpr);
75+
static Expr *build_bound_expr(Expr *elemExpr, Datum val,
76+
bool isLowerBound, bool isInclusive,
77+
TypeCacheEntry *typeCache,
78+
Oid opfamily, Oid rng_collation);
7279

7380

7481
/*
@@ -2173,6 +2180,58 @@ make_empty_range(TypeCacheEntry *typcache)
21732180
return make_range(typcache, &lower, &upper, true, NULL);
21742181
}
21752182

2183+
/*
2184+
* Planner support function for elem_contained_by_range (<@ operator).
2185+
*/
2186+
Datum
2187+
elem_contained_by_range_support(PG_FUNCTION_ARGS)
2188+
{
2189+
Node *rawreq = (Node *) PG_GETARG_POINTER(0);
2190+
Node *ret = NULL;
2191+
2192+
if (IsA(rawreq, SupportRequestSimplify))
2193+
{
2194+
SupportRequestSimplify *req = (SupportRequestSimplify *) rawreq;
2195+
FuncExpr *fexpr = req->fcall;
2196+
Expr *leftop,
2197+
*rightop;
2198+
2199+
Assert(list_length(fexpr->args) == 2);
2200+
leftop = linitial(fexpr->args);
2201+
rightop = lsecond(fexpr->args);
2202+
2203+
ret = find_simplified_clause(req->root, rightop, leftop);
2204+
}
2205+
2206+
PG_RETURN_POINTER(ret);
2207+
}
2208+
2209+
/*
2210+
* Planner support function for range_contains_elem (@> operator).
2211+
*/
2212+
Datum
2213+
range_contains_elem_support(PG_FUNCTION_ARGS)
2214+
{
2215+
Node *rawreq = (Node *) PG_GETARG_POINTER(0);
2216+
Node *ret = NULL;
2217+
2218+
if (IsA(rawreq, SupportRequestSimplify))
2219+
{
2220+
SupportRequestSimplify *req = (SupportRequestSimplify *) rawreq;
2221+
FuncExpr *fexpr = req->fcall;
2222+
Expr *leftop,
2223+
*rightop;
2224+
2225+
Assert(list_length(fexpr->args) == 2);
2226+
leftop = linitial(fexpr->args);
2227+
rightop = lsecond(fexpr->args);
2228+
2229+
ret = find_simplified_clause(req->root, leftop, rightop);
2230+
}
2231+
2232+
PG_RETURN_POINTER(ret);
2233+
}
2234+
21762235

21772236
/*
21782237
*----------------------------------------------------------
@@ -2715,3 +2774,180 @@ datum_write(Pointer ptr, Datum datum, bool typbyval, char typalign,
27152774

27162775
return ptr;
27172776
}
2777+
2778+
/*
2779+
* Common code for the elem_contained_by_range and range_contains_elem
2780+
* support functions. The caller has extracted the function argument
2781+
* expressions, and swapped them if necessary to pass the range first.
2782+
*
2783+
* Returns a simplified replacement expression, or NULL if we can't simplify.
2784+
*/
2785+
static Node *
2786+
find_simplified_clause(PlannerInfo *root, Expr *rangeExpr, Expr *elemExpr)
2787+
{
2788+
RangeType *range;
2789+
TypeCacheEntry *rangetypcache;
2790+
RangeBound lower;
2791+
RangeBound upper;
2792+
bool empty;
2793+
2794+
/* can't do anything unless the range is a non-null constant */
2795+
if (!IsA(rangeExpr, Const) || ((Const *) rangeExpr)->constisnull)
2796+
return NULL;
2797+
range = DatumGetRangeTypeP(((Const *) rangeExpr)->constvalue);
2798+
2799+
rangetypcache = lookup_type_cache(RangeTypeGetOid(range),
2800+
TYPECACHE_RANGE_INFO);
2801+
if (rangetypcache->rngelemtype == NULL)
2802+
elog(ERROR, "type %u is not a range type", RangeTypeGetOid(range));
2803+
2804+
range_deserialize(rangetypcache, range, &lower, &upper, &empty);
2805+
2806+
if (empty)
2807+
{
2808+
/* if the range is empty, then there can be no matches */
2809+
return makeBoolConst(false, false);
2810+
}
2811+
else if (lower.infinite && upper.infinite)
2812+
{
2813+
/* the range has infinite bounds, so it matches everything */
2814+
return makeBoolConst(true, false);
2815+
}
2816+
else
2817+
{
2818+
/* at least one bound is available, we have something to work with */
2819+
TypeCacheEntry *elemTypcache = rangetypcache->rngelemtype;
2820+
Oid opfamily = rangetypcache->rng_opfamily;
2821+
Oid rng_collation = rangetypcache->rng_collation;
2822+
Expr *lowerExpr = NULL;
2823+
Expr *upperExpr = NULL;
2824+
2825+
if (!lower.infinite && !upper.infinite)
2826+
{
2827+
/*
2828+
* When both bounds are present, we have a problem: the
2829+
* "simplified" clause would need to evaluate the elemExpr twice.
2830+
* That's definitely not okay if the elemExpr is volatile, and
2831+
* it's also unattractive if the elemExpr is expensive.
2832+
*/
2833+
QualCost eval_cost;
2834+
2835+
if (contain_volatile_functions((Node *) elemExpr))
2836+
return NULL;
2837+
2838+
/*
2839+
* We define "expensive" as "contains any subplan or more than 10
2840+
* operators". Note that the subplan search has to be done
2841+
* explicitly, since cost_qual_eval() will barf on unplanned
2842+
* subselects.
2843+
*/
2844+
if (contain_subplans((Node *) elemExpr))
2845+
return NULL;
2846+
cost_qual_eval_node(&eval_cost, (Node *) elemExpr, root);
2847+
if (eval_cost.startup + eval_cost.per_tuple >
2848+
10 * cpu_operator_cost)
2849+
return NULL;
2850+
}
2851+
2852+
/* Okay, try to build boundary comparison expressions */
2853+
if (!lower.infinite)
2854+
{
2855+
lowerExpr = build_bound_expr(elemExpr,
2856+
lower.val,
2857+
true,
2858+
lower.inclusive,
2859+
elemTypcache,
2860+
opfamily,
2861+
rng_collation);
2862+
if (lowerExpr == NULL)
2863+
return NULL;
2864+
}
2865+
2866+
if (!upper.infinite)
2867+
{
2868+
/* Copy the elemExpr if we need two copies */
2869+
if (!lower.infinite)
2870+
elemExpr = copyObject(elemExpr);
2871+
upperExpr = build_bound_expr(elemExpr,
2872+
upper.val,
2873+
false,
2874+
upper.inclusive,
2875+
elemTypcache,
2876+
opfamily,
2877+
rng_collation);
2878+
if (upperExpr == NULL)
2879+
return NULL;
2880+
}
2881+
2882+
if (lowerExpr != NULL && upperExpr != NULL)
2883+
return (Node *) make_andclause(list_make2(lowerExpr, upperExpr));
2884+
else if (lowerExpr != NULL)
2885+
return (Node *) lowerExpr;
2886+
else if (upperExpr != NULL)
2887+
return (Node *) upperExpr;
2888+
else
2889+
{
2890+
Assert(false);
2891+
return NULL;
2892+
}
2893+
}
2894+
}
2895+
2896+
/*
2897+
* Helper function for find_simplified_clause().
2898+
*
2899+
* Build the expression (elemExpr Operator val), where the operator is
2900+
* the appropriate member of the given opfamily depending on
2901+
* isLowerBound and isInclusive. typeCache is the typcache entry for
2902+
* the "val" value (presently, this will be the same type as elemExpr).
2903+
* rng_collation is the collation to use in the comparison.
2904+
*
2905+
* Return NULL on failure (if, for some reason, we can't find the operator).
2906+
*/
2907+
static Expr *
2908+
build_bound_expr(Expr *elemExpr, Datum val,
2909+
bool isLowerBound, bool isInclusive,
2910+
TypeCacheEntry *typeCache,
2911+
Oid opfamily, Oid rng_collation)
2912+
{
2913+
Oid elemType = typeCache->type_id;
2914+
int16 elemTypeLen = typeCache->typlen;
2915+
bool elemByValue = typeCache->typbyval;
2916+
Oid elemCollation = typeCache->typcollation;
2917+
int16 strategy;
2918+
Oid oproid;
2919+
Expr *constExpr;
2920+
2921+
/* Identify the comparison operator to use */
2922+
if (isLowerBound)
2923+
strategy = isInclusive ? BTGreaterEqualStrategyNumber : BTGreaterStrategyNumber;
2924+
else
2925+
strategy = isInclusive ? BTLessEqualStrategyNumber : BTLessStrategyNumber;
2926+
2927+
/*
2928+
* We could use exprType(elemExpr) here, if it ever becomes possible that
2929+
* elemExpr is not the exact same type as the range elements.
2930+
*/
2931+
oproid = get_opfamily_member(opfamily, elemType, elemType, strategy);
2932+
2933+
/* We don't really expect failure here, but just in case ... */
2934+
if (!OidIsValid(oproid))
2935+
return NULL;
2936+
2937+
/* OK, convert "val" to a full-fledged Const node, and make the OpExpr */
2938+
constExpr = (Expr *) makeConst(elemType,
2939+
-1,
2940+
elemCollation,
2941+
elemTypeLen,
2942+
val,
2943+
false,
2944+
elemByValue);
2945+
2946+
return make_opclause(oproid,
2947+
BOOLOID,
2948+
false,
2949+
elemExpr,
2950+
constExpr,
2951+
InvalidOid,
2952+
rng_collation);
2953+
}

src/backend/utils/adt/rangetypes_selfuncs.c

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -196,9 +196,10 @@ rangesel(PG_FUNCTION_ARGS)
196196
else if (operator == OID_RANGE_ELEM_CONTAINED_OP)
197197
{
198198
/*
199-
* Here, the Var is the elem, not the range. For now we just punt and
200-
* return the default estimate. In future we could disassemble the
201-
* range constant and apply scalarineqsel ...
199+
* Here, the Var is the elem, not the range. In typical cases
200+
* elem_contained_by_range_support will have simplified this case, so
201+
* that we won't get here. If we do get here we'll fall back on a
202+
* default estimate.
202203
*/
203204
}
204205
else if (((Const *) other)->consttype == vardata.vartype)

src/backend/utils/cache/typcache.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -940,6 +940,7 @@ load_rangetype_info(TypeCacheEntry *typentry)
940940
/* get opclass properties and look up the comparison function */
941941
opfamilyOid = get_opclass_family(opclassOid);
942942
opcintype = get_opclass_input_type(opclassOid);
943+
typentry->rng_opfamily = opfamilyOid;
943944

944945
cmpFnOid = get_opfamily_proc(opfamilyOid, opcintype, opcintype,
945946
BTORDER_PROC);

src/include/catalog/catversion.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,6 @@
5757
*/
5858

5959
/* yyyymmddN */
60-
#define CATALOG_VERSION_NO 202401191
60+
#define CATALOG_VERSION_NO 202401201
6161

6262
#endif

src/include/catalog/pg_proc.dat

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10503,13 +10503,15 @@
1050310503
proname => 'range_overlaps', prorettype => 'bool',
1050410504
proargtypes => 'anyrange anyrange', prosrc => 'range_overlaps' },
1050510505
{ oid => '3858',
10506-
proname => 'range_contains_elem', prorettype => 'bool',
10507-
proargtypes => 'anyrange anyelement', prosrc => 'range_contains_elem' },
10506+
proname => 'range_contains_elem', prosupport => 'range_contains_elem_support',
10507+
prorettype => 'bool', proargtypes => 'anyrange anyelement',
10508+
prosrc => 'range_contains_elem' },
1050810509
{ oid => '3859',
1050910510
proname => 'range_contains', prorettype => 'bool',
1051010511
proargtypes => 'anyrange anyrange', prosrc => 'range_contains' },
1051110512
{ oid => '3860',
10512-
proname => 'elem_contained_by_range', prorettype => 'bool',
10513+
proname => 'elem_contained_by_range',
10514+
prosupport => 'elem_contained_by_range_support', prorettype => 'bool',
1051310515
proargtypes => 'anyelement anyrange', prosrc => 'elem_contained_by_range' },
1051410516
{ oid => '3861',
1051510517
proname => 'range_contained_by', prorettype => 'bool',
@@ -10532,6 +10534,12 @@
1053210534
{ oid => '3867',
1053310535
proname => 'range_union', prorettype => 'anyrange',
1053410536
proargtypes => 'anyrange anyrange', prosrc => 'range_union' },
10537+
{ oid => '9998', descr => 'planner support for range_contains_elem',
10538+
proname => 'range_contains_elem_support', prorettype => 'internal',
10539+
proargtypes => 'internal', prosrc => 'range_contains_elem_support' },
10540+
{ oid => '9999', descr => 'planner support for elem_contained_by_range',
10541+
proname => 'elem_contained_by_range_support', prorettype => 'internal',
10542+
proargtypes => 'internal', prosrc => 'elem_contained_by_range_support' },
1053510543
{ oid => '4057',
1053610544
descr => 'the smallest range which includes both of the given ranges',
1053710545
proname => 'range_merge', prorettype => 'anyrange',

src/include/utils/typcache.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ typedef struct TypeCacheEntry
9696
* btree comparison function.
9797
*/
9898
struct TypeCacheEntry *rngelemtype; /* range's element type */
99+
Oid rng_opfamily; /* opfamily to use for range comparisons */
99100
Oid rng_collation; /* collation for comparisons, if any */
100101
FmgrInfo rng_cmp_proc_finfo; /* comparison function */
101102
FmgrInfo rng_canonical_finfo; /* canonicalization function, if any */

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy