mirror of
https://github.com/postgres/postgres.git
synced 2025-04-27 22:56:53 +03:00
The mess cleaned up in commit da0759600 is clear evidence that it's a bug hazard to expect the caller of get_attstatsslot()/free_attstatsslot() to provide the correct type OID for the array elements in the slot. Moreover, we weren't even getting any performance benefit from that, since get_attstatsslot() was extracting the real type OID from the array anyway. So we ought to get rid of that requirement; indeed, it would make more sense for get_attstatsslot() to pass back the type OID it found, in case the caller isn't sure what to expect, which is likely in binary- compatible-operator cases. Another problem with the current implementation is that if the stats array element type is pass-by-reference, we incur a palloc/memcpy/pfree cycle for each element. That seemed acceptable when the code was written because we were targeting O(10) array sizes --- but these days, stats arrays are almost always bigger than that, sometimes much bigger. We can save a significant number of cycles by doing one palloc/memcpy/pfree of the whole array. Indeed, in the now-probably-common case where the array is toasted, that happens anyway so this method is basically free. (Note: although the catcache code will inline any out-of-line toasted values, it doesn't decompress them. At the other end of the size range, it doesn't expand short-header datums either. In either case, DatumGetArrayTypeP would have to make a copy. We do end up using an extra array copy step if the element type is pass-by-value and the array length is neither small enough for a short header nor large enough to have suffered compression. But that seems like a very acceptable price for winning in pass-by-ref cases.) Hence, redesign to take these insights into account. While at it, convert to an API in which we fill a struct rather than passing a bunch of pointers to individual output arguments. That will make it less painful if we ever want further expansion of what get_attstatsslot can pass back. It's certainly arguable that this is new development and not something to push post-feature-freeze. However, I view it as primarily bug-proofing and therefore something that's better to have sooner not later. Since we aren't quite at beta phase yet, let's put it in. Discussion: https://postgr.es/m/16364.1494520862@sss.pgh.pa.us
972 lines
31 KiB
C
972 lines
31 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* network_selfuncs.c
|
|
* Functions for selectivity estimation of inet/cidr operators
|
|
*
|
|
* This module provides estimators for the subnet inclusion and overlap
|
|
* operators. Estimates are based on null fraction, most common values,
|
|
* and histogram of inet/cidr columns.
|
|
*
|
|
* Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
*
|
|
*
|
|
* IDENTIFICATION
|
|
* src/backend/utils/adt/network_selfuncs.c
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
#include "postgres.h"
|
|
|
|
#include <math.h>
|
|
|
|
#include "access/htup_details.h"
|
|
#include "catalog/pg_operator.h"
|
|
#include "catalog/pg_statistic.h"
|
|
#include "utils/builtins.h"
|
|
#include "utils/inet.h"
|
|
#include "utils/lsyscache.h"
|
|
#include "utils/selfuncs.h"
|
|
|
|
|
|
/* Default selectivity for the inet overlap operator */
|
|
#define DEFAULT_OVERLAP_SEL 0.01
|
|
|
|
/* Default selectivity for the various inclusion operators */
|
|
#define DEFAULT_INCLUSION_SEL 0.005
|
|
|
|
/* Default selectivity for specified operator */
|
|
#define DEFAULT_SEL(operator) \
|
|
((operator) == OID_INET_OVERLAP_OP ? \
|
|
DEFAULT_OVERLAP_SEL : DEFAULT_INCLUSION_SEL)
|
|
|
|
/* Maximum number of items to consider in join selectivity calculations */
|
|
#define MAX_CONSIDERED_ELEMS 1024
|
|
|
|
static Selectivity networkjoinsel_inner(Oid operator,
|
|
VariableStatData *vardata1, VariableStatData *vardata2);
|
|
static Selectivity networkjoinsel_semi(Oid operator,
|
|
VariableStatData *vardata1, VariableStatData *vardata2);
|
|
static Selectivity mcv_population(float4 *mcv_numbers, int mcv_nvalues);
|
|
static Selectivity inet_hist_value_sel(Datum *values, int nvalues,
|
|
Datum constvalue, int opr_codenum);
|
|
static Selectivity inet_mcv_join_sel(Datum *mcv1_values,
|
|
float4 *mcv1_numbers, int mcv1_nvalues, Datum *mcv2_values,
|
|
float4 *mcv2_numbers, int mcv2_nvalues, Oid operator);
|
|
static Selectivity inet_mcv_hist_sel(Datum *mcv_values, float4 *mcv_numbers,
|
|
int mcv_nvalues, Datum *hist_values, int hist_nvalues,
|
|
int opr_codenum);
|
|
static Selectivity inet_hist_inclusion_join_sel(Datum *hist1_values,
|
|
int hist1_nvalues,
|
|
Datum *hist2_values, int hist2_nvalues,
|
|
int opr_codenum);
|
|
static Selectivity inet_semi_join_sel(Datum lhs_value,
|
|
bool mcv_exists, Datum *mcv_values, int mcv_nvalues,
|
|
bool hist_exists, Datum *hist_values, int hist_nvalues,
|
|
double hist_weight,
|
|
FmgrInfo *proc, int opr_codenum);
|
|
static int inet_opr_codenum(Oid operator);
|
|
static int inet_inclusion_cmp(inet *left, inet *right, int opr_codenum);
|
|
static int inet_masklen_inclusion_cmp(inet *left, inet *right,
|
|
int opr_codenum);
|
|
static int inet_hist_match_divider(inet *boundary, inet *query,
|
|
int opr_codenum);
|
|
|
|
/*
|
|
* Selectivity estimation for the subnet inclusion/overlap operators
|
|
*/
|
|
Datum
|
|
networksel(PG_FUNCTION_ARGS)
|
|
{
|
|
PlannerInfo *root = (PlannerInfo *) PG_GETARG_POINTER(0);
|
|
Oid operator = PG_GETARG_OID(1);
|
|
List *args = (List *) PG_GETARG_POINTER(2);
|
|
int varRelid = PG_GETARG_INT32(3);
|
|
VariableStatData vardata;
|
|
Node *other;
|
|
bool varonleft;
|
|
Selectivity selec,
|
|
mcv_selec,
|
|
non_mcv_selec;
|
|
Datum constvalue;
|
|
Form_pg_statistic stats;
|
|
AttStatsSlot hslot;
|
|
double sumcommon,
|
|
nullfrac;
|
|
FmgrInfo proc;
|
|
|
|
/*
|
|
* If expression is not (variable op something) or (something op
|
|
* variable), then punt and return a default estimate.
|
|
*/
|
|
if (!get_restriction_variable(root, args, varRelid,
|
|
&vardata, &other, &varonleft))
|
|
PG_RETURN_FLOAT8(DEFAULT_SEL(operator));
|
|
|
|
/*
|
|
* Can't do anything useful if the something is not a constant, either.
|
|
*/
|
|
if (!IsA(other, Const))
|
|
{
|
|
ReleaseVariableStats(vardata);
|
|
PG_RETURN_FLOAT8(DEFAULT_SEL(operator));
|
|
}
|
|
|
|
/* All of the operators handled here are strict. */
|
|
if (((Const *) other)->constisnull)
|
|
{
|
|
ReleaseVariableStats(vardata);
|
|
PG_RETURN_FLOAT8(0.0);
|
|
}
|
|
constvalue = ((Const *) other)->constvalue;
|
|
|
|
/* Otherwise, we need stats in order to produce a non-default estimate. */
|
|
if (!HeapTupleIsValid(vardata.statsTuple))
|
|
{
|
|
ReleaseVariableStats(vardata);
|
|
PG_RETURN_FLOAT8(DEFAULT_SEL(operator));
|
|
}
|
|
|
|
stats = (Form_pg_statistic) GETSTRUCT(vardata.statsTuple);
|
|
nullfrac = stats->stanullfrac;
|
|
|
|
/*
|
|
* If we have most-common-values info, add up the fractions of the MCV
|
|
* entries that satisfy MCV OP CONST. These fractions contribute directly
|
|
* to the result selectivity. Also add up the total fraction represented
|
|
* by MCV entries.
|
|
*/
|
|
fmgr_info(get_opcode(operator), &proc);
|
|
mcv_selec = mcv_selectivity(&vardata, &proc, constvalue, varonleft,
|
|
&sumcommon);
|
|
|
|
/*
|
|
* If we have a histogram, use it to estimate the proportion of the
|
|
* non-MCV population that satisfies the clause. If we don't, apply the
|
|
* default selectivity to that population.
|
|
*/
|
|
if (get_attstatsslot(&hslot, vardata.statsTuple,
|
|
STATISTIC_KIND_HISTOGRAM, InvalidOid,
|
|
ATTSTATSSLOT_VALUES))
|
|
{
|
|
int opr_codenum = inet_opr_codenum(operator);
|
|
|
|
/* Commute if needed, so we can consider histogram to be on the left */
|
|
if (!varonleft)
|
|
opr_codenum = -opr_codenum;
|
|
non_mcv_selec = inet_hist_value_sel(hslot.values, hslot.nvalues,
|
|
constvalue, opr_codenum);
|
|
|
|
free_attstatsslot(&hslot);
|
|
}
|
|
else
|
|
non_mcv_selec = DEFAULT_SEL(operator);
|
|
|
|
/* Combine selectivities for MCV and non-MCV populations */
|
|
selec = mcv_selec + (1.0 - nullfrac - sumcommon) * non_mcv_selec;
|
|
|
|
/* Result should be in range, but make sure... */
|
|
CLAMP_PROBABILITY(selec);
|
|
|
|
ReleaseVariableStats(vardata);
|
|
|
|
PG_RETURN_FLOAT8(selec);
|
|
}
|
|
|
|
/*
|
|
* Join selectivity estimation for the subnet inclusion/overlap operators
|
|
*
|
|
* This function has the same structure as eqjoinsel() in selfuncs.c.
|
|
*
|
|
* Throughout networkjoinsel and its subroutines, we have a performance issue
|
|
* in that the amount of work to be done is O(N^2) in the length of the MCV
|
|
* and histogram arrays. To keep the runtime from getting out of hand when
|
|
* large statistics targets have been set, we arbitrarily limit the number of
|
|
* values considered to 1024 (MAX_CONSIDERED_ELEMS). For the MCV arrays, this
|
|
* is easy: just consider at most the first N elements. (Since the MCVs are
|
|
* sorted by decreasing frequency, this correctly gets us the first N MCVs.)
|
|
* For the histogram arrays, we decimate; that is consider only every k'th
|
|
* element, where k is chosen so that no more than MAX_CONSIDERED_ELEMS
|
|
* elements are considered. This should still give us a good random sample of
|
|
* the non-MCV population. Decimation is done on-the-fly in the loops that
|
|
* iterate over the histogram arrays.
|
|
*/
|
|
Datum
|
|
networkjoinsel(PG_FUNCTION_ARGS)
|
|
{
|
|
PlannerInfo *root = (PlannerInfo *) PG_GETARG_POINTER(0);
|
|
Oid operator = PG_GETARG_OID(1);
|
|
List *args = (List *) PG_GETARG_POINTER(2);
|
|
#ifdef NOT_USED
|
|
JoinType jointype = (JoinType) PG_GETARG_INT16(3);
|
|
#endif
|
|
SpecialJoinInfo *sjinfo = (SpecialJoinInfo *) PG_GETARG_POINTER(4);
|
|
double selec;
|
|
VariableStatData vardata1;
|
|
VariableStatData vardata2;
|
|
bool join_is_reversed;
|
|
|
|
get_join_variables(root, args, sjinfo,
|
|
&vardata1, &vardata2, &join_is_reversed);
|
|
|
|
switch (sjinfo->jointype)
|
|
{
|
|
case JOIN_INNER:
|
|
case JOIN_LEFT:
|
|
case JOIN_FULL:
|
|
|
|
/*
|
|
* Selectivity for left/full join is not exactly the same as inner
|
|
* join, but we neglect the difference, as eqjoinsel does.
|
|
*/
|
|
selec = networkjoinsel_inner(operator, &vardata1, &vardata2);
|
|
break;
|
|
case JOIN_SEMI:
|
|
case JOIN_ANTI:
|
|
/* Here, it's important that we pass the outer var on the left. */
|
|
if (!join_is_reversed)
|
|
selec = networkjoinsel_semi(operator, &vardata1, &vardata2);
|
|
else
|
|
selec = networkjoinsel_semi(get_commutator(operator),
|
|
&vardata2, &vardata1);
|
|
break;
|
|
default:
|
|
/* other values not expected here */
|
|
elog(ERROR, "unrecognized join type: %d",
|
|
(int) sjinfo->jointype);
|
|
selec = 0; /* keep compiler quiet */
|
|
break;
|
|
}
|
|
|
|
ReleaseVariableStats(vardata1);
|
|
ReleaseVariableStats(vardata2);
|
|
|
|
CLAMP_PROBABILITY(selec);
|
|
|
|
PG_RETURN_FLOAT8((float8) selec);
|
|
}
|
|
|
|
/*
|
|
* Inner join selectivity estimation for subnet inclusion/overlap operators
|
|
*
|
|
* Calculates MCV vs MCV, MCV vs histogram and histogram vs histogram
|
|
* selectivity for join using the subnet inclusion operators. Unlike the
|
|
* join selectivity function for the equality operator, eqjoinsel_inner(),
|
|
* one to one matching of the values is not enough. Network inclusion
|
|
* operators are likely to match many to many, so we must check all pairs.
|
|
* (Note: it might be possible to exploit understanding of the histogram's
|
|
* btree ordering to reduce the work needed, but we don't currently try.)
|
|
* Also, MCV vs histogram selectivity is not neglected as in eqjoinsel_inner().
|
|
*/
|
|
static Selectivity
|
|
networkjoinsel_inner(Oid operator,
|
|
VariableStatData *vardata1, VariableStatData *vardata2)
|
|
{
|
|
Form_pg_statistic stats;
|
|
double nullfrac1 = 0.0,
|
|
nullfrac2 = 0.0;
|
|
Selectivity selec = 0.0,
|
|
sumcommon1 = 0.0,
|
|
sumcommon2 = 0.0;
|
|
bool mcv1_exists = false,
|
|
mcv2_exists = false,
|
|
hist1_exists = false,
|
|
hist2_exists = false;
|
|
int opr_codenum;
|
|
int mcv1_length = 0,
|
|
mcv2_length = 0;
|
|
AttStatsSlot mcv1_slot;
|
|
AttStatsSlot mcv2_slot;
|
|
AttStatsSlot hist1_slot;
|
|
AttStatsSlot hist2_slot;
|
|
|
|
if (HeapTupleIsValid(vardata1->statsTuple))
|
|
{
|
|
stats = (Form_pg_statistic) GETSTRUCT(vardata1->statsTuple);
|
|
nullfrac1 = stats->stanullfrac;
|
|
|
|
mcv1_exists = get_attstatsslot(&mcv1_slot, vardata1->statsTuple,
|
|
STATISTIC_KIND_MCV, InvalidOid,
|
|
ATTSTATSSLOT_VALUES | ATTSTATSSLOT_NUMBERS);
|
|
hist1_exists = get_attstatsslot(&hist1_slot, vardata1->statsTuple,
|
|
STATISTIC_KIND_HISTOGRAM, InvalidOid,
|
|
ATTSTATSSLOT_VALUES);
|
|
/* Arbitrarily limit number of MCVs considered */
|
|
mcv1_length = Min(mcv1_slot.nvalues, MAX_CONSIDERED_ELEMS);
|
|
if (mcv1_exists)
|
|
sumcommon1 = mcv_population(mcv1_slot.numbers, mcv1_length);
|
|
}
|
|
else
|
|
{
|
|
memset(&mcv1_slot, 0, sizeof(mcv1_slot));
|
|
memset(&hist1_slot, 0, sizeof(hist1_slot));
|
|
}
|
|
|
|
if (HeapTupleIsValid(vardata2->statsTuple))
|
|
{
|
|
stats = (Form_pg_statistic) GETSTRUCT(vardata2->statsTuple);
|
|
nullfrac2 = stats->stanullfrac;
|
|
|
|
mcv2_exists = get_attstatsslot(&mcv2_slot, vardata2->statsTuple,
|
|
STATISTIC_KIND_MCV, InvalidOid,
|
|
ATTSTATSSLOT_VALUES | ATTSTATSSLOT_NUMBERS);
|
|
hist2_exists = get_attstatsslot(&hist2_slot, vardata2->statsTuple,
|
|
STATISTIC_KIND_HISTOGRAM, InvalidOid,
|
|
ATTSTATSSLOT_VALUES);
|
|
/* Arbitrarily limit number of MCVs considered */
|
|
mcv2_length = Min(mcv2_slot.nvalues, MAX_CONSIDERED_ELEMS);
|
|
if (mcv2_exists)
|
|
sumcommon2 = mcv_population(mcv2_slot.numbers, mcv2_length);
|
|
}
|
|
else
|
|
{
|
|
memset(&mcv2_slot, 0, sizeof(mcv2_slot));
|
|
memset(&hist2_slot, 0, sizeof(hist2_slot));
|
|
}
|
|
|
|
opr_codenum = inet_opr_codenum(operator);
|
|
|
|
/*
|
|
* Calculate selectivity for MCV vs MCV matches.
|
|
*/
|
|
if (mcv1_exists && mcv2_exists)
|
|
selec += inet_mcv_join_sel(mcv1_slot.values, mcv1_slot.numbers,
|
|
mcv1_length,
|
|
mcv2_slot.values, mcv2_slot.numbers,
|
|
mcv2_length,
|
|
operator);
|
|
|
|
/*
|
|
* Add in selectivities for MCV vs histogram matches, scaling according to
|
|
* the fractions of the populations represented by the histograms. Note
|
|
* that the second case needs to commute the operator.
|
|
*/
|
|
if (mcv1_exists && hist2_exists)
|
|
selec += (1.0 - nullfrac2 - sumcommon2) *
|
|
inet_mcv_hist_sel(mcv1_slot.values, mcv1_slot.numbers, mcv1_length,
|
|
hist2_slot.values, hist2_slot.nvalues,
|
|
opr_codenum);
|
|
if (mcv2_exists && hist1_exists)
|
|
selec += (1.0 - nullfrac1 - sumcommon1) *
|
|
inet_mcv_hist_sel(mcv2_slot.values, mcv2_slot.numbers, mcv2_length,
|
|
hist1_slot.values, hist1_slot.nvalues,
|
|
-opr_codenum);
|
|
|
|
/*
|
|
* Add in selectivity for histogram vs histogram matches, again scaling
|
|
* appropriately.
|
|
*/
|
|
if (hist1_exists && hist2_exists)
|
|
selec += (1.0 - nullfrac1 - sumcommon1) *
|
|
(1.0 - nullfrac2 - sumcommon2) *
|
|
inet_hist_inclusion_join_sel(hist1_slot.values, hist1_slot.nvalues,
|
|
hist2_slot.values, hist2_slot.nvalues,
|
|
opr_codenum);
|
|
|
|
/*
|
|
* If useful statistics are not available then use the default estimate.
|
|
* We can apply null fractions if known, though.
|
|
*/
|
|
if ((!mcv1_exists && !hist1_exists) || (!mcv2_exists && !hist2_exists))
|
|
selec = (1.0 - nullfrac1) * (1.0 - nullfrac2) * DEFAULT_SEL(operator);
|
|
|
|
/* Release stats. */
|
|
free_attstatsslot(&mcv1_slot);
|
|
free_attstatsslot(&mcv2_slot);
|
|
free_attstatsslot(&hist1_slot);
|
|
free_attstatsslot(&hist2_slot);
|
|
|
|
return selec;
|
|
}
|
|
|
|
/*
|
|
* Semi join selectivity estimation for subnet inclusion/overlap operators
|
|
*
|
|
* Calculates MCV vs MCV, MCV vs histogram, histogram vs MCV, and histogram vs
|
|
* histogram selectivity for semi/anti join cases.
|
|
*/
|
|
static Selectivity
|
|
networkjoinsel_semi(Oid operator,
|
|
VariableStatData *vardata1, VariableStatData *vardata2)
|
|
{
|
|
Form_pg_statistic stats;
|
|
Selectivity selec = 0.0,
|
|
sumcommon1 = 0.0,
|
|
sumcommon2 = 0.0;
|
|
double nullfrac1 = 0.0,
|
|
nullfrac2 = 0.0,
|
|
hist2_weight = 0.0;
|
|
bool mcv1_exists = false,
|
|
mcv2_exists = false,
|
|
hist1_exists = false,
|
|
hist2_exists = false;
|
|
int opr_codenum;
|
|
FmgrInfo proc;
|
|
int i,
|
|
mcv1_length = 0,
|
|
mcv2_length = 0;
|
|
AttStatsSlot mcv1_slot;
|
|
AttStatsSlot mcv2_slot;
|
|
AttStatsSlot hist1_slot;
|
|
AttStatsSlot hist2_slot;
|
|
|
|
if (HeapTupleIsValid(vardata1->statsTuple))
|
|
{
|
|
stats = (Form_pg_statistic) GETSTRUCT(vardata1->statsTuple);
|
|
nullfrac1 = stats->stanullfrac;
|
|
|
|
mcv1_exists = get_attstatsslot(&mcv1_slot, vardata1->statsTuple,
|
|
STATISTIC_KIND_MCV, InvalidOid,
|
|
ATTSTATSSLOT_VALUES | ATTSTATSSLOT_NUMBERS);
|
|
hist1_exists = get_attstatsslot(&hist1_slot, vardata1->statsTuple,
|
|
STATISTIC_KIND_HISTOGRAM, InvalidOid,
|
|
ATTSTATSSLOT_VALUES);
|
|
/* Arbitrarily limit number of MCVs considered */
|
|
mcv1_length = Min(mcv1_slot.nvalues, MAX_CONSIDERED_ELEMS);
|
|
if (mcv1_exists)
|
|
sumcommon1 = mcv_population(mcv1_slot.numbers, mcv1_length);
|
|
}
|
|
else
|
|
{
|
|
memset(&mcv1_slot, 0, sizeof(mcv1_slot));
|
|
memset(&hist1_slot, 0, sizeof(hist1_slot));
|
|
}
|
|
|
|
if (HeapTupleIsValid(vardata2->statsTuple))
|
|
{
|
|
stats = (Form_pg_statistic) GETSTRUCT(vardata2->statsTuple);
|
|
nullfrac2 = stats->stanullfrac;
|
|
|
|
mcv2_exists = get_attstatsslot(&mcv2_slot, vardata2->statsTuple,
|
|
STATISTIC_KIND_MCV, InvalidOid,
|
|
ATTSTATSSLOT_VALUES | ATTSTATSSLOT_NUMBERS);
|
|
hist2_exists = get_attstatsslot(&hist2_slot, vardata2->statsTuple,
|
|
STATISTIC_KIND_HISTOGRAM, InvalidOid,
|
|
ATTSTATSSLOT_VALUES);
|
|
/* Arbitrarily limit number of MCVs considered */
|
|
mcv2_length = Min(mcv2_slot.nvalues, MAX_CONSIDERED_ELEMS);
|
|
if (mcv2_exists)
|
|
sumcommon2 = mcv_population(mcv2_slot.numbers, mcv2_length);
|
|
}
|
|
else
|
|
{
|
|
memset(&mcv2_slot, 0, sizeof(mcv2_slot));
|
|
memset(&hist2_slot, 0, sizeof(hist2_slot));
|
|
}
|
|
|
|
opr_codenum = inet_opr_codenum(operator);
|
|
fmgr_info(get_opcode(operator), &proc);
|
|
|
|
/* Estimate number of input rows represented by RHS histogram. */
|
|
if (hist2_exists && vardata2->rel)
|
|
hist2_weight = (1.0 - nullfrac2 - sumcommon2) * vardata2->rel->rows;
|
|
|
|
/*
|
|
* Consider each element of the LHS MCV list, matching it to whatever RHS
|
|
* stats we have. Scale according to the known frequency of the MCV.
|
|
*/
|
|
if (mcv1_exists && (mcv2_exists || hist2_exists))
|
|
{
|
|
for (i = 0; i < mcv1_length; i++)
|
|
{
|
|
selec += mcv1_slot.numbers[i] *
|
|
inet_semi_join_sel(mcv1_slot.values[i],
|
|
mcv2_exists, mcv2_slot.values, mcv2_length,
|
|
hist2_exists,
|
|
hist2_slot.values, hist2_slot.nvalues,
|
|
hist2_weight,
|
|
&proc, opr_codenum);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Consider each element of the LHS histogram, except for the first and
|
|
* last elements, which we exclude on the grounds that they're outliers
|
|
* and thus not very representative. Scale on the assumption that each
|
|
* such histogram element represents an equal share of the LHS histogram
|
|
* population (which is a bit bogus, because the members of its bucket may
|
|
* not all act the same with respect to the join clause, but it's hard to
|
|
* do better).
|
|
*
|
|
* If there are too many histogram elements, decimate to limit runtime.
|
|
*/
|
|
if (hist1_exists && hist1_slot.nvalues > 2 && (mcv2_exists || hist2_exists))
|
|
{
|
|
double hist_selec_sum = 0.0;
|
|
int k,
|
|
n;
|
|
|
|
k = (hist1_slot.nvalues - 3) / MAX_CONSIDERED_ELEMS + 1;
|
|
|
|
n = 0;
|
|
for (i = 1; i < hist1_slot.nvalues - 1; i += k)
|
|
{
|
|
hist_selec_sum +=
|
|
inet_semi_join_sel(hist1_slot.values[i],
|
|
mcv2_exists, mcv2_slot.values, mcv2_length,
|
|
hist2_exists,
|
|
hist2_slot.values, hist2_slot.nvalues,
|
|
hist2_weight,
|
|
&proc, opr_codenum);
|
|
n++;
|
|
}
|
|
|
|
selec += (1.0 - nullfrac1 - sumcommon1) * hist_selec_sum / n;
|
|
}
|
|
|
|
/*
|
|
* If useful statistics are not available then use the default estimate.
|
|
* We can apply null fractions if known, though.
|
|
*/
|
|
if ((!mcv1_exists && !hist1_exists) || (!mcv2_exists && !hist2_exists))
|
|
selec = (1.0 - nullfrac1) * (1.0 - nullfrac2) * DEFAULT_SEL(operator);
|
|
|
|
/* Release stats. */
|
|
free_attstatsslot(&mcv1_slot);
|
|
free_attstatsslot(&mcv2_slot);
|
|
free_attstatsslot(&hist1_slot);
|
|
free_attstatsslot(&hist2_slot);
|
|
|
|
return selec;
|
|
}
|
|
|
|
/*
|
|
* Compute the fraction of a relation's population that is represented
|
|
* by the MCV list.
|
|
*/
|
|
static Selectivity
|
|
mcv_population(float4 *mcv_numbers, int mcv_nvalues)
|
|
{
|
|
Selectivity sumcommon = 0.0;
|
|
int i;
|
|
|
|
for (i = 0; i < mcv_nvalues; i++)
|
|
{
|
|
sumcommon += mcv_numbers[i];
|
|
}
|
|
|
|
return sumcommon;
|
|
}
|
|
|
|
/*
|
|
* Inet histogram vs single value selectivity estimation
|
|
*
|
|
* Estimate the fraction of the histogram population that satisfies
|
|
* "value OPR CONST". (The result needs to be scaled to reflect the
|
|
* proportion of the total population represented by the histogram.)
|
|
*
|
|
* The histogram is originally for the inet btree comparison operators.
|
|
* Only the common bits of the network part and the length of the network part
|
|
* (masklen) are interesting for the subnet inclusion operators. Fortunately,
|
|
* btree comparison treats the network part as the major sort key. Even so,
|
|
* the length of the network part would not really be significant in the
|
|
* histogram. This would lead to big mistakes for data sets with uneven
|
|
* masklen distribution. To reduce this problem, comparisons with the left
|
|
* and the right sides of the buckets are used together.
|
|
*
|
|
* Histogram bucket matches are calculated in two forms. If the constant
|
|
* matches both bucket endpoints the bucket is considered as fully matched.
|
|
* The second form is to match the bucket partially; we recognize this when
|
|
* the constant matches just one endpoint, or the two endpoints fall on
|
|
* opposite sides of the constant. (Note that when the constant matches an
|
|
* interior histogram element, it gets credit for partial matches to the
|
|
* buckets on both sides, while a match to a histogram endpoint gets credit
|
|
* for only one partial match. This is desirable.)
|
|
*
|
|
* The divider in the partial bucket match is imagined as the distance
|
|
* between the decisive bits and the common bits of the addresses. It will
|
|
* be used as a power of two as it is the natural scale for the IP network
|
|
* inclusion. This partial bucket match divider calculation is an empirical
|
|
* formula and subject to change with more experiment.
|
|
*
|
|
* For a partial match, we try to calculate dividers for both of the
|
|
* boundaries. If the address family of a boundary value does not match the
|
|
* constant or comparison of the length of the network parts is not correct
|
|
* for the operator, the divider for that boundary will not be taken into
|
|
* account. If both of the dividers are valid, the greater one will be used
|
|
* to minimize the mistake in buckets that have disparate masklens. This
|
|
* calculation is unfair when dividers can be calculated for both of the
|
|
* boundaries but they are far from each other; but it is not a common
|
|
* situation as the boundaries are expected to share most of their significant
|
|
* bits of their masklens. The mistake would be greater, if we would use the
|
|
* minimum instead of the maximum, and we don't know a sensible way to combine
|
|
* them.
|
|
*
|
|
* For partial match in buckets that have different address families on the
|
|
* left and right sides, only the boundary with the same address family is
|
|
* taken into consideration. This can cause more mistakes for these buckets
|
|
* if the masklens of their boundaries are also disparate. But this can only
|
|
* happen in one bucket, since only two address families exist. It seems a
|
|
* better option than not considering these buckets at all.
|
|
*/
|
|
static Selectivity
|
|
inet_hist_value_sel(Datum *values, int nvalues, Datum constvalue,
|
|
int opr_codenum)
|
|
{
|
|
Selectivity match = 0.0;
|
|
inet *query,
|
|
*left,
|
|
*right;
|
|
int i,
|
|
k,
|
|
n;
|
|
int left_order,
|
|
right_order,
|
|
left_divider,
|
|
right_divider;
|
|
|
|
/* guard against zero-divide below */
|
|
if (nvalues <= 1)
|
|
return 0.0;
|
|
|
|
/* if there are too many histogram elements, decimate to limit runtime */
|
|
k = (nvalues - 2) / MAX_CONSIDERED_ELEMS + 1;
|
|
|
|
query = DatumGetInetPP(constvalue);
|
|
|
|
/* "left" is the left boundary value of the current bucket ... */
|
|
left = DatumGetInetPP(values[0]);
|
|
left_order = inet_inclusion_cmp(left, query, opr_codenum);
|
|
|
|
n = 0;
|
|
for (i = k; i < nvalues; i += k)
|
|
{
|
|
/* ... and "right" is the right boundary value */
|
|
right = DatumGetInetPP(values[i]);
|
|
right_order = inet_inclusion_cmp(right, query, opr_codenum);
|
|
|
|
if (left_order == 0 && right_order == 0)
|
|
{
|
|
/* The whole bucket matches, since both endpoints do. */
|
|
match += 1.0;
|
|
}
|
|
else if ((left_order <= 0 && right_order >= 0) ||
|
|
(left_order >= 0 && right_order <= 0))
|
|
{
|
|
/* Partial bucket match. */
|
|
left_divider = inet_hist_match_divider(left, query, opr_codenum);
|
|
right_divider = inet_hist_match_divider(right, query, opr_codenum);
|
|
|
|
if (left_divider >= 0 || right_divider >= 0)
|
|
match += 1.0 / pow(2.0, Max(left_divider, right_divider));
|
|
}
|
|
|
|
/* Shift the variables. */
|
|
left = right;
|
|
left_order = right_order;
|
|
|
|
/* Count the number of buckets considered. */
|
|
n++;
|
|
}
|
|
|
|
return match / n;
|
|
}
|
|
|
|
/*
|
|
* Inet MCV vs MCV join selectivity estimation
|
|
*
|
|
* We simply add up the fractions of the populations that satisfy the clause.
|
|
* The result is exact and does not need to be scaled further.
|
|
*/
|
|
static Selectivity
|
|
inet_mcv_join_sel(Datum *mcv1_values, float4 *mcv1_numbers, int mcv1_nvalues,
|
|
Datum *mcv2_values, float4 *mcv2_numbers, int mcv2_nvalues,
|
|
Oid operator)
|
|
{
|
|
Selectivity selec = 0.0;
|
|
FmgrInfo proc;
|
|
int i,
|
|
j;
|
|
|
|
fmgr_info(get_opcode(operator), &proc);
|
|
|
|
for (i = 0; i < mcv1_nvalues; i++)
|
|
{
|
|
for (j = 0; j < mcv2_nvalues; j++)
|
|
if (DatumGetBool(FunctionCall2(&proc,
|
|
mcv1_values[i],
|
|
mcv2_values[j])))
|
|
selec += mcv1_numbers[i] * mcv2_numbers[j];
|
|
}
|
|
return selec;
|
|
}
|
|
|
|
/*
|
|
* Inet MCV vs histogram join selectivity estimation
|
|
*
|
|
* For each MCV on the lefthand side, estimate the fraction of the righthand's
|
|
* histogram population that satisfies the join clause, and add those up,
|
|
* scaling by the MCV's frequency. The result still needs to be scaled
|
|
* according to the fraction of the righthand's population represented by
|
|
* the histogram.
|
|
*/
|
|
static Selectivity
|
|
inet_mcv_hist_sel(Datum *mcv_values, float4 *mcv_numbers, int mcv_nvalues,
|
|
Datum *hist_values, int hist_nvalues,
|
|
int opr_codenum)
|
|
{
|
|
Selectivity selec = 0.0;
|
|
int i;
|
|
|
|
/*
|
|
* We'll call inet_hist_value_selec with the histogram on the left, so we
|
|
* must commute the operator.
|
|
*/
|
|
opr_codenum = -opr_codenum;
|
|
|
|
for (i = 0; i < mcv_nvalues; i++)
|
|
{
|
|
selec += mcv_numbers[i] *
|
|
inet_hist_value_sel(hist_values, hist_nvalues, mcv_values[i],
|
|
opr_codenum);
|
|
}
|
|
return selec;
|
|
}
|
|
|
|
/*
|
|
* Inet histogram vs histogram join selectivity estimation
|
|
*
|
|
* Here, we take all values listed in the second histogram (except for the
|
|
* first and last elements, which are excluded on the grounds of possibly
|
|
* not being very representative) and treat them as a uniform sample of
|
|
* the non-MCV population for that relation. For each one, we apply
|
|
* inet_hist_value_selec to see what fraction of the first histogram
|
|
* it matches.
|
|
*
|
|
* We could alternatively do this the other way around using the operator's
|
|
* commutator. XXX would it be worthwhile to do it both ways and take the
|
|
* average? That would at least avoid non-commutative estimation results.
|
|
*/
|
|
static Selectivity
|
|
inet_hist_inclusion_join_sel(Datum *hist1_values, int hist1_nvalues,
|
|
Datum *hist2_values, int hist2_nvalues,
|
|
int opr_codenum)
|
|
{
|
|
double match = 0.0;
|
|
int i,
|
|
k,
|
|
n;
|
|
|
|
if (hist2_nvalues <= 2)
|
|
return 0.0; /* no interior histogram elements */
|
|
|
|
/* if there are too many histogram elements, decimate to limit runtime */
|
|
k = (hist2_nvalues - 3) / MAX_CONSIDERED_ELEMS + 1;
|
|
|
|
n = 0;
|
|
for (i = 1; i < hist2_nvalues - 1; i += k)
|
|
{
|
|
match += inet_hist_value_sel(hist1_values, hist1_nvalues,
|
|
hist2_values[i], opr_codenum);
|
|
n++;
|
|
}
|
|
|
|
return match / n;
|
|
}
|
|
|
|
/*
|
|
* Inet semi join selectivity estimation for one value
|
|
*
|
|
* The function calculates the probability that there is at least one row
|
|
* in the RHS table that satisfies the "lhs_value op column" condition.
|
|
* It is used in semi join estimation to check a sample from the left hand
|
|
* side table.
|
|
*
|
|
* The MCV and histogram from the right hand side table should be provided as
|
|
* arguments with the lhs_value from the left hand side table for the join.
|
|
* hist_weight is the total number of rows represented by the histogram.
|
|
* For example, if the table has 1000 rows, and 10% of the rows are in the MCV
|
|
* list, and another 10% are NULLs, hist_weight would be 800.
|
|
*
|
|
* First, the lhs_value will be matched to the most common values. If it
|
|
* matches any of them, 1.0 will be returned, because then there is surely
|
|
* a match.
|
|
*
|
|
* Otherwise, the histogram will be used to estimate the number of rows in
|
|
* the second table that match the condition. If the estimate is greater
|
|
* than 1.0, 1.0 will be returned, because it means there is a greater chance
|
|
* that the lhs_value will match more than one row in the table. If it is
|
|
* between 0.0 and 1.0, it will be returned as the probability.
|
|
*/
|
|
static Selectivity
|
|
inet_semi_join_sel(Datum lhs_value,
|
|
bool mcv_exists, Datum *mcv_values, int mcv_nvalues,
|
|
bool hist_exists, Datum *hist_values, int hist_nvalues,
|
|
double hist_weight,
|
|
FmgrInfo *proc, int opr_codenum)
|
|
{
|
|
if (mcv_exists)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < mcv_nvalues; i++)
|
|
{
|
|
if (DatumGetBool(FunctionCall2(proc,
|
|
lhs_value,
|
|
mcv_values[i])))
|
|
return 1.0;
|
|
}
|
|
}
|
|
|
|
if (hist_exists && hist_weight > 0)
|
|
{
|
|
Selectivity hist_selec;
|
|
|
|
/* Commute operator, since we're passing lhs_value on the right */
|
|
hist_selec = inet_hist_value_sel(hist_values, hist_nvalues,
|
|
lhs_value, -opr_codenum);
|
|
|
|
if (hist_selec > 0)
|
|
return Min(1.0, hist_weight * hist_selec);
|
|
}
|
|
|
|
return 0.0;
|
|
}
|
|
|
|
/*
|
|
* Assign useful code numbers for the subnet inclusion/overlap operators
|
|
*
|
|
* Only inet_masklen_inclusion_cmp() and inet_hist_match_divider() depend
|
|
* on the exact codes assigned here; but many other places in this file
|
|
* know that they can negate a code to obtain the code for the commutator
|
|
* operator.
|
|
*/
|
|
static int
|
|
inet_opr_codenum(Oid operator)
|
|
{
|
|
switch (operator)
|
|
{
|
|
case OID_INET_SUP_OP:
|
|
return -2;
|
|
case OID_INET_SUPEQ_OP:
|
|
return -1;
|
|
case OID_INET_OVERLAP_OP:
|
|
return 0;
|
|
case OID_INET_SUBEQ_OP:
|
|
return 1;
|
|
case OID_INET_SUB_OP:
|
|
return 2;
|
|
default:
|
|
elog(ERROR, "unrecognized operator %u for inet selectivity",
|
|
operator);
|
|
}
|
|
return 0; /* unreached, but keep compiler quiet */
|
|
}
|
|
|
|
/*
|
|
* Comparison function for the subnet inclusion/overlap operators
|
|
*
|
|
* If the comparison is okay for the specified inclusion operator, the return
|
|
* value will be 0. Otherwise the return value will be less than or greater
|
|
* than 0 as appropriate for the operator.
|
|
*
|
|
* Comparison is compatible with the basic comparison function for the inet
|
|
* type. See network_cmp_internal() in network.c for the original. Basic
|
|
* comparison operators are implemented with the network_cmp_internal()
|
|
* function. It is possible to implement the subnet inclusion operators with
|
|
* this function.
|
|
*
|
|
* Comparison is first on the common bits of the network part, then on the
|
|
* length of the network part (masklen) as in the network_cmp_internal()
|
|
* function. Only the first part is in this function. The second part is
|
|
* separated to another function for reusability. The difference between the
|
|
* second part and the original network_cmp_internal() is that the inclusion
|
|
* operator is considered while comparing the lengths of the network parts.
|
|
* See the inet_masklen_inclusion_cmp() function below.
|
|
*/
|
|
static int
|
|
inet_inclusion_cmp(inet *left, inet *right, int opr_codenum)
|
|
{
|
|
if (ip_family(left) == ip_family(right))
|
|
{
|
|
int order;
|
|
|
|
order = bitncmp(ip_addr(left), ip_addr(right),
|
|
Min(ip_bits(left), ip_bits(right)));
|
|
if (order != 0)
|
|
return order;
|
|
|
|
return inet_masklen_inclusion_cmp(left, right, opr_codenum);
|
|
}
|
|
|
|
return ip_family(left) - ip_family(right);
|
|
}
|
|
|
|
/*
|
|
* Masklen comparison function for the subnet inclusion/overlap operators
|
|
*
|
|
* Compares the lengths of the network parts of the inputs. If the comparison
|
|
* is okay for the specified inclusion operator, the return value will be 0.
|
|
* Otherwise the return value will be less than or greater than 0 as
|
|
* appropriate for the operator.
|
|
*/
|
|
static int
|
|
inet_masklen_inclusion_cmp(inet *left, inet *right, int opr_codenum)
|
|
{
|
|
int order;
|
|
|
|
order = (int) ip_bits(left) - (int) ip_bits(right);
|
|
|
|
/*
|
|
* Return 0 if the operator would accept this combination of masklens.
|
|
* Note that opr_codenum zero (overlaps) will accept all cases.
|
|
*/
|
|
if ((order > 0 && opr_codenum >= 0) ||
|
|
(order == 0 && opr_codenum >= -1 && opr_codenum <= 1) ||
|
|
(order < 0 && opr_codenum <= 0))
|
|
return 0;
|
|
|
|
/*
|
|
* Otherwise, return a negative value for sup/supeq (notionally, the RHS
|
|
* needs to have a larger masklen than it has, which would make it sort
|
|
* later), or a positive value for sub/subeq (vice versa).
|
|
*/
|
|
return opr_codenum;
|
|
}
|
|
|
|
/*
|
|
* Inet histogram partial match divider calculation
|
|
*
|
|
* First the families and the lengths of the network parts are compared using
|
|
* the subnet inclusion operator. If those are acceptable for the operator,
|
|
* the divider will be calculated using the masklens and the common bits of
|
|
* the addresses. -1 will be returned if it cannot be calculated.
|
|
*
|
|
* See commentary for inet_hist_value_sel() for some rationale for this.
|
|
*/
|
|
static int
|
|
inet_hist_match_divider(inet *boundary, inet *query, int opr_codenum)
|
|
{
|
|
if (ip_family(boundary) == ip_family(query) &&
|
|
inet_masklen_inclusion_cmp(boundary, query, opr_codenum) == 0)
|
|
{
|
|
int min_bits,
|
|
decisive_bits;
|
|
|
|
min_bits = Min(ip_bits(boundary), ip_bits(query));
|
|
|
|
/*
|
|
* Set decisive_bits to the masklen of the one that should contain the
|
|
* other according to the operator.
|
|
*/
|
|
if (opr_codenum < 0)
|
|
decisive_bits = ip_bits(boundary);
|
|
else if (opr_codenum > 0)
|
|
decisive_bits = ip_bits(query);
|
|
else
|
|
decisive_bits = min_bits;
|
|
|
|
/*
|
|
* Now return the number of non-common decisive bits. (This will be
|
|
* zero if the boundary and query in fact match, else positive.)
|
|
*/
|
|
if (min_bits > 0)
|
|
return decisive_bits - bitncommon(ip_addr(boundary),
|
|
ip_addr(query),
|
|
min_bits);
|
|
return decisive_bits;
|
|
}
|
|
|
|
return -1;
|
|
}
|