mirror of
https://github.com/postgres/postgres.git
synced 2025-06-13 07:41:39 +03:00
Teach planner about the idea that a mergejoin won't necessarily read
both input streams to the end. If one variable's range is much less than the other, an indexscan-based merge can win by not scanning all of the other table. Per example from Reinhard Max.
This commit is contained in:
@ -8,7 +8,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $Header: /cvsroot/pgsql/src/backend/executor/nodeMergejoin.c,v 1.47 2001/10/28 06:25:43 momjian Exp $
|
* $Header: /cvsroot/pgsql/src/backend/executor/nodeMergejoin.c,v 1.48 2002/03/01 04:09:22 tgl Exp $
|
||||||
*
|
*
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
@ -88,97 +88,62 @@ static bool MergeCompare(List *eqQual, List *compareQual, ExprContext *econtext)
|
|||||||
|
|
||||||
|
|
||||||
/* ----------------------------------------------------------------
|
/* ----------------------------------------------------------------
|
||||||
* MJFormSkipQual
|
* MJFormSkipQuals
|
||||||
*
|
*
|
||||||
* This takes the mergeclause which is a qualification of the
|
* This takes the mergeclause which is a qualification of the
|
||||||
* form ((= expr expr) (= expr expr) ...) and forms a new
|
* form ((= expr expr) (= expr expr) ...) and forms new lists
|
||||||
* qualification like ((> expr expr) (> expr expr) ...) which
|
* of the forms ((< expr expr) (< expr expr) ...) and
|
||||||
* is used by ExecMergeJoin() in order to determine if we should
|
* ((> expr expr) (> expr expr) ...). These lists will be used
|
||||||
* skip tuples. The replacement operators are named either ">"
|
* by ExecMergeJoin() to determine if we should skip tuples.
|
||||||
* or "<" according to the replaceopname parameter, and have the
|
* (We expect there to be suitable operators because the "=" operators
|
||||||
* same operand data types as the "=" operators they replace.
|
|
||||||
* (We expect there to be such operators because the "=" operators
|
|
||||||
* were marked mergejoinable; however, there might be a different
|
* were marked mergejoinable; however, there might be a different
|
||||||
* one needed in each qual clause.)
|
* one needed in each qual clause.)
|
||||||
* ----------------------------------------------------------------
|
* ----------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
static List *
|
static void
|
||||||
MJFormSkipQual(List *qualList, char *replaceopname)
|
MJFormSkipQuals(List *qualList, List **ltQuals, List **gtQuals)
|
||||||
{
|
{
|
||||||
List *qualCopy;
|
List *ltcdr,
|
||||||
List *qualcdr;
|
*gtcdr;
|
||||||
Expr *qual;
|
|
||||||
Oper *op;
|
|
||||||
HeapTuple optup;
|
|
||||||
Form_pg_operator opform;
|
|
||||||
Oid oprleft,
|
|
||||||
oprright;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* qualList is a list: ((op .. ..) ...)
|
* Make modifiable copies of the qualList.
|
||||||
*
|
|
||||||
* first we make a copy of it. copyObject() makes a deep copy so let's
|
|
||||||
* use it instead of the old fashoned lispCopy()...
|
|
||||||
*/
|
*/
|
||||||
qualCopy = (List *) copyObject((Node *) qualList);
|
*ltQuals = (List *) copyObject((Node *) qualList);
|
||||||
|
*gtQuals = (List *) copyObject((Node *) qualList);
|
||||||
|
|
||||||
foreach(qualcdr, qualCopy)
|
/*
|
||||||
|
* Scan both lists in parallel, so that we can update the operators
|
||||||
|
* with the minimum number of syscache searches.
|
||||||
|
*/
|
||||||
|
ltcdr = *ltQuals;
|
||||||
|
foreach(gtcdr, *gtQuals)
|
||||||
{
|
{
|
||||||
/*
|
Expr *ltqual = (Expr *) lfirst(ltcdr);
|
||||||
* first get the current (op .. ..) list
|
Expr *gtqual = (Expr *) lfirst(gtcdr);
|
||||||
*/
|
Oper *ltop = (Oper *) ltqual->oper;
|
||||||
qual = lfirst(qualcdr);
|
Oper *gtop = (Oper *) gtqual->oper;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* now get at the op
|
* The two ops should be identical, so use either one for lookup.
|
||||||
*/
|
*/
|
||||||
op = (Oper *) qual->oper;
|
if (!IsA(ltop, Oper))
|
||||||
if (!IsA(op, Oper))
|
elog(ERROR, "MJFormSkipQuals: op not an Oper!");
|
||||||
elog(ERROR, "MJFormSkipQual: op not an Oper!");
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Get the declared left and right operand types of the operator.
|
* Lookup the operators, and replace the data in the copied
|
||||||
* Note we do *not* use the actual operand types, since those
|
* operator nodes.
|
||||||
* might be different in scenarios with binary-compatible data
|
|
||||||
* types. There should be "<" and ">" operators matching a
|
|
||||||
* mergejoinable "=" operator's declared operand types, but we
|
|
||||||
* might not find them if we search with the actual operand types.
|
|
||||||
*/
|
*/
|
||||||
optup = SearchSysCache(OPEROID,
|
op_mergejoin_crossops(ltop->opno,
|
||||||
ObjectIdGetDatum(op->opno),
|
<op->opno,
|
||||||
0, 0, 0);
|
>op->opno,
|
||||||
if (!HeapTupleIsValid(optup)) /* shouldn't happen */
|
<op->opid,
|
||||||
elog(ERROR, "MJFormSkipQual: operator %u not found", op->opno);
|
>op->opid);
|
||||||
opform = (Form_pg_operator) GETSTRUCT(optup);
|
ltop->op_fcache = NULL;
|
||||||
oprleft = opform->oprleft;
|
gtop->op_fcache = NULL;
|
||||||
oprright = opform->oprright;
|
|
||||||
ReleaseSysCache(optup);
|
|
||||||
|
|
||||||
/*
|
ltcdr = lnext(ltcdr);
|
||||||
* Now look up the matching "<" or ">" operator. If there isn't
|
|
||||||
* one, whoever marked the "=" operator mergejoinable was a loser.
|
|
||||||
*/
|
|
||||||
optup = SearchSysCache(OPERNAME,
|
|
||||||
PointerGetDatum(replaceopname),
|
|
||||||
ObjectIdGetDatum(oprleft),
|
|
||||||
ObjectIdGetDatum(oprright),
|
|
||||||
CharGetDatum('b'));
|
|
||||||
if (!HeapTupleIsValid(optup))
|
|
||||||
elog(ERROR,
|
|
||||||
"MJFormSkipQual: mergejoin operator %u has no matching %s op",
|
|
||||||
op->opno, replaceopname);
|
|
||||||
opform = (Form_pg_operator) GETSTRUCT(optup);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* And replace the data in the copied operator node.
|
|
||||||
*/
|
|
||||||
op->opno = optup->t_data->t_oid;
|
|
||||||
op->opid = opform->oprcode;
|
|
||||||
op->op_fcache = NULL;
|
|
||||||
ReleaseSysCache(optup);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return qualCopy;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ----------------------------------------------------------------
|
/* ----------------------------------------------------------------
|
||||||
@ -1430,7 +1395,6 @@ bool
|
|||||||
ExecInitMergeJoin(MergeJoin *node, EState *estate, Plan *parent)
|
ExecInitMergeJoin(MergeJoin *node, EState *estate, Plan *parent)
|
||||||
{
|
{
|
||||||
MergeJoinState *mergestate;
|
MergeJoinState *mergestate;
|
||||||
List *joinclauses;
|
|
||||||
|
|
||||||
MJ1_printf("ExecInitMergeJoin: %s\n",
|
MJ1_printf("ExecInitMergeJoin: %s\n",
|
||||||
"initializing node");
|
"initializing node");
|
||||||
@ -1522,9 +1486,9 @@ ExecInitMergeJoin(MergeJoin *node, EState *estate, Plan *parent)
|
|||||||
/*
|
/*
|
||||||
* form merge skip qualifications
|
* form merge skip qualifications
|
||||||
*/
|
*/
|
||||||
joinclauses = node->mergeclauses;
|
MJFormSkipQuals(node->mergeclauses,
|
||||||
mergestate->mj_OuterSkipQual = MJFormSkipQual(joinclauses, "<");
|
&mergestate->mj_OuterSkipQual,
|
||||||
mergestate->mj_InnerSkipQual = MJFormSkipQual(joinclauses, ">");
|
&mergestate->mj_InnerSkipQual);
|
||||||
|
|
||||||
MJ_printf("\nExecInitMergeJoin: OuterSkipQual is ");
|
MJ_printf("\nExecInitMergeJoin: OuterSkipQual is ");
|
||||||
MJ_nodeDisplay(mergestate->mj_OuterSkipQual);
|
MJ_nodeDisplay(mergestate->mj_OuterSkipQual);
|
||||||
|
@ -42,7 +42,7 @@
|
|||||||
* Portions Copyright (c) 1994, Regents of the University of California
|
* Portions Copyright (c) 1994, Regents of the University of California
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $Header: /cvsroot/pgsql/src/backend/optimizer/path/costsize.c,v 1.79 2001/10/25 05:49:32 momjian Exp $
|
* $Header: /cvsroot/pgsql/src/backend/optimizer/path/costsize.c,v 1.80 2002/03/01 04:09:24 tgl Exp $
|
||||||
*
|
*
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
@ -58,6 +58,7 @@
|
|||||||
#include "optimizer/cost.h"
|
#include "optimizer/cost.h"
|
||||||
#include "optimizer/pathnode.h"
|
#include "optimizer/pathnode.h"
|
||||||
#include "parser/parsetree.h"
|
#include "parser/parsetree.h"
|
||||||
|
#include "utils/selfuncs.h"
|
||||||
#include "utils/lsyscache.h"
|
#include "utils/lsyscache.h"
|
||||||
#include "utils/syscache.h"
|
#include "utils/syscache.h"
|
||||||
|
|
||||||
@ -565,12 +566,29 @@ cost_mergejoin(Path *path, Query *root,
|
|||||||
Cost startup_cost = 0;
|
Cost startup_cost = 0;
|
||||||
Cost run_cost = 0;
|
Cost run_cost = 0;
|
||||||
Cost cpu_per_tuple;
|
Cost cpu_per_tuple;
|
||||||
|
double outer_rows,
|
||||||
|
inner_rows;
|
||||||
double ntuples;
|
double ntuples;
|
||||||
|
Selectivity leftscan,
|
||||||
|
rightscan;
|
||||||
Path sort_path; /* dummy for result of cost_sort */
|
Path sort_path; /* dummy for result of cost_sort */
|
||||||
|
|
||||||
if (!enable_mergejoin)
|
if (!enable_mergejoin)
|
||||||
startup_cost += disable_cost;
|
startup_cost += disable_cost;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A merge join will stop as soon as it exhausts either input stream.
|
||||||
|
* Estimate fraction of the left and right inputs that will actually
|
||||||
|
* need to be scanned. We use only the first (most significant)
|
||||||
|
* merge clause for this purpose.
|
||||||
|
*/
|
||||||
|
mergejoinscansel(root,
|
||||||
|
(Node *) ((RestrictInfo *) lfirst(mergeclauses))->clause,
|
||||||
|
&leftscan, &rightscan);
|
||||||
|
|
||||||
|
outer_rows = outer_path->parent->rows * leftscan;
|
||||||
|
inner_rows = inner_path->parent->rows * rightscan;
|
||||||
|
|
||||||
/* cost of source data */
|
/* cost of source data */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -588,12 +606,14 @@ cost_mergejoin(Path *path, Query *root,
|
|||||||
outer_path->parent->rows,
|
outer_path->parent->rows,
|
||||||
outer_path->parent->width);
|
outer_path->parent->width);
|
||||||
startup_cost += sort_path.startup_cost;
|
startup_cost += sort_path.startup_cost;
|
||||||
run_cost += sort_path.total_cost - sort_path.startup_cost;
|
run_cost += (sort_path.total_cost - sort_path.startup_cost)
|
||||||
|
* leftscan;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
startup_cost += outer_path->startup_cost;
|
startup_cost += outer_path->startup_cost;
|
||||||
run_cost += outer_path->total_cost - outer_path->startup_cost;
|
run_cost += (outer_path->total_cost - outer_path->startup_cost)
|
||||||
|
* leftscan;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (innersortkeys) /* do we need to sort inner? */
|
if (innersortkeys) /* do we need to sort inner? */
|
||||||
@ -605,30 +625,33 @@ cost_mergejoin(Path *path, Query *root,
|
|||||||
inner_path->parent->rows,
|
inner_path->parent->rows,
|
||||||
inner_path->parent->width);
|
inner_path->parent->width);
|
||||||
startup_cost += sort_path.startup_cost;
|
startup_cost += sort_path.startup_cost;
|
||||||
run_cost += sort_path.total_cost - sort_path.startup_cost;
|
run_cost += (sort_path.total_cost - sort_path.startup_cost)
|
||||||
|
* rightscan;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
startup_cost += inner_path->startup_cost;
|
startup_cost += inner_path->startup_cost;
|
||||||
run_cost += inner_path->total_cost - inner_path->startup_cost;
|
run_cost += (inner_path->total_cost - inner_path->startup_cost)
|
||||||
|
* rightscan;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The number of tuple comparisons needed depends drastically on the
|
* The number of tuple comparisons needed depends drastically on the
|
||||||
* number of equal keys in the two source relations, which we have no
|
* number of equal keys in the two source relations, which we have no
|
||||||
* good way of estimating. Somewhat arbitrarily, we charge one tuple
|
* good way of estimating. (XXX could the MCV statistics help?)
|
||||||
|
* Somewhat arbitrarily, we charge one tuple
|
||||||
* comparison (one cpu_operator_cost) for each tuple in the two source
|
* comparison (one cpu_operator_cost) for each tuple in the two source
|
||||||
* relations. This is probably a lower bound.
|
* relations. This is probably a lower bound.
|
||||||
*/
|
*/
|
||||||
run_cost += cpu_operator_cost *
|
run_cost += cpu_operator_cost * (outer_rows + inner_rows);
|
||||||
(outer_path->parent->rows + inner_path->parent->rows);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* For each tuple that gets through the mergejoin proper, we charge
|
* For each tuple that gets through the mergejoin proper, we charge
|
||||||
* cpu_tuple_cost plus the cost of evaluating additional restriction
|
* cpu_tuple_cost plus the cost of evaluating additional restriction
|
||||||
* clauses that are to be applied at the join. It's OK to use an
|
* clauses that are to be applied at the join. It's OK to use an
|
||||||
* approximate selectivity here, since in most cases this is a minor
|
* approximate selectivity here, since in most cases this is a minor
|
||||||
* component of the cost.
|
* component of the cost. NOTE: it's correct to use the unscaled rows
|
||||||
|
* counts here, not the scaled-down counts we obtained above.
|
||||||
*/
|
*/
|
||||||
ntuples = approx_selectivity(root, mergeclauses) *
|
ntuples = approx_selectivity(root, mergeclauses) *
|
||||||
outer_path->parent->rows * inner_path->parent->rows;
|
outer_path->parent->rows * inner_path->parent->rows;
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $Header: /cvsroot/pgsql/src/backend/utils/adt/selfuncs.c,v 1.103 2002/01/03 04:02:34 tgl Exp $
|
* $Header: /cvsroot/pgsql/src/backend/utils/adt/selfuncs.c,v 1.104 2002/03/01 04:09:25 tgl Exp $
|
||||||
*
|
*
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
@ -92,11 +92,13 @@
|
|||||||
#include "parser/parsetree.h"
|
#include "parser/parsetree.h"
|
||||||
#include "utils/builtins.h"
|
#include "utils/builtins.h"
|
||||||
#include "utils/date.h"
|
#include "utils/date.h"
|
||||||
|
#include "utils/datum.h"
|
||||||
#include "utils/int8.h"
|
#include "utils/int8.h"
|
||||||
#include "utils/lsyscache.h"
|
#include "utils/lsyscache.h"
|
||||||
#include "utils/selfuncs.h"
|
#include "utils/selfuncs.h"
|
||||||
#include "utils/syscache.h"
|
#include "utils/syscache.h"
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Note: the default selectivity estimates are not chosen entirely at random.
|
* Note: the default selectivity estimates are not chosen entirely at random.
|
||||||
* We want them to be small enough to ensure that indexscans will be used if
|
* We want them to be small enough to ensure that indexscans will be used if
|
||||||
@ -137,6 +139,7 @@
|
|||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
|
|
||||||
|
static bool get_var_maximum(Query *root, Var *var, Oid sortop, Datum *max);
|
||||||
static bool convert_to_scalar(Datum value, Oid valuetypid, double *scaledvalue,
|
static bool convert_to_scalar(Datum value, Oid valuetypid, double *scaledvalue,
|
||||||
Datum lobound, Datum hibound, Oid boundstypid,
|
Datum lobound, Datum hibound, Oid boundstypid,
|
||||||
double *scaledlobound, double *scaledhibound);
|
double *scaledlobound, double *scaledhibound);
|
||||||
@ -419,7 +422,9 @@ neqsel(PG_FUNCTION_ARGS)
|
|||||||
*
|
*
|
||||||
* This is the guts of both scalarltsel and scalargtsel. The caller has
|
* This is the guts of both scalarltsel and scalargtsel. The caller has
|
||||||
* commuted the clause, if necessary, so that we can treat the Var as
|
* commuted the clause, if necessary, so that we can treat the Var as
|
||||||
* being on the left.
|
* being on the left. The caller must also make sure that the other side
|
||||||
|
* of the clause is a non-null Const, and dissect same into a value and
|
||||||
|
* datatype.
|
||||||
*
|
*
|
||||||
* This routine works for any datatype (or pair of datatypes) known to
|
* This routine works for any datatype (or pair of datatypes) known to
|
||||||
* convert_to_scalar(). If it is applied to some other datatype,
|
* convert_to_scalar(). If it is applied to some other datatype,
|
||||||
@ -427,11 +432,9 @@ neqsel(PG_FUNCTION_ARGS)
|
|||||||
*/
|
*/
|
||||||
static double
|
static double
|
||||||
scalarineqsel(Query *root, Oid operator, bool isgt,
|
scalarineqsel(Query *root, Oid operator, bool isgt,
|
||||||
Var *var, Node *other)
|
Var *var, Datum constval, Oid consttype)
|
||||||
{
|
{
|
||||||
Oid relid;
|
Oid relid;
|
||||||
Datum constval;
|
|
||||||
Oid consttype;
|
|
||||||
HeapTuple statsTuple;
|
HeapTuple statsTuple;
|
||||||
Form_pg_statistic stats;
|
Form_pg_statistic stats;
|
||||||
FmgrInfo opproc;
|
FmgrInfo opproc;
|
||||||
@ -454,22 +457,6 @@ scalarineqsel(Query *root, Oid operator, bool isgt,
|
|||||||
if (relid == InvalidOid)
|
if (relid == InvalidOid)
|
||||||
return DEFAULT_INEQ_SEL;
|
return DEFAULT_INEQ_SEL;
|
||||||
|
|
||||||
/*
|
|
||||||
* Can't do anything useful if the something is not a constant,
|
|
||||||
* either.
|
|
||||||
*/
|
|
||||||
if (!IsA(other, Const))
|
|
||||||
return DEFAULT_INEQ_SEL;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* If the constant is NULL, assume operator is strict and return zero,
|
|
||||||
* ie, operator will never return TRUE.
|
|
||||||
*/
|
|
||||||
if (((Const *) other)->constisnull)
|
|
||||||
return 0.0;
|
|
||||||
constval = ((Const *) other)->constvalue;
|
|
||||||
consttype = ((Const *) other)->consttype;
|
|
||||||
|
|
||||||
/* get stats for the attribute */
|
/* get stats for the attribute */
|
||||||
statsTuple = SearchSysCache(STATRELATT,
|
statsTuple = SearchSysCache(STATRELATT,
|
||||||
ObjectIdGetDatum(relid),
|
ObjectIdGetDatum(relid),
|
||||||
@ -697,6 +684,8 @@ scalarltsel(PG_FUNCTION_ARGS)
|
|||||||
int varRelid = PG_GETARG_INT32(3);
|
int varRelid = PG_GETARG_INT32(3);
|
||||||
Var *var;
|
Var *var;
|
||||||
Node *other;
|
Node *other;
|
||||||
|
Datum constval;
|
||||||
|
Oid consttype;
|
||||||
bool varonleft;
|
bool varonleft;
|
||||||
bool isgt;
|
bool isgt;
|
||||||
double selec;
|
double selec;
|
||||||
@ -710,6 +699,22 @@ scalarltsel(PG_FUNCTION_ARGS)
|
|||||||
&var, &other, &varonleft))
|
&var, &other, &varonleft))
|
||||||
PG_RETURN_FLOAT8(DEFAULT_INEQ_SEL);
|
PG_RETURN_FLOAT8(DEFAULT_INEQ_SEL);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Can't do anything useful if the something is not a constant,
|
||||||
|
* either.
|
||||||
|
*/
|
||||||
|
if (!IsA(other, Const))
|
||||||
|
PG_RETURN_FLOAT8(DEFAULT_INEQ_SEL);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If the constant is NULL, assume operator is strict and return zero,
|
||||||
|
* ie, operator will never return TRUE.
|
||||||
|
*/
|
||||||
|
if (((Const *) other)->constisnull)
|
||||||
|
PG_RETURN_FLOAT8(0.0);
|
||||||
|
constval = ((Const *) other)->constvalue;
|
||||||
|
consttype = ((Const *) other)->consttype;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Force the var to be on the left to simplify logic in scalarineqsel.
|
* Force the var to be on the left to simplify logic in scalarineqsel.
|
||||||
*/
|
*/
|
||||||
@ -730,7 +735,7 @@ scalarltsel(PG_FUNCTION_ARGS)
|
|||||||
isgt = true;
|
isgt = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
selec = scalarineqsel(root, operator, isgt, var, other);
|
selec = scalarineqsel(root, operator, isgt, var, constval, consttype);
|
||||||
|
|
||||||
PG_RETURN_FLOAT8((float8) selec);
|
PG_RETURN_FLOAT8((float8) selec);
|
||||||
}
|
}
|
||||||
@ -747,6 +752,8 @@ scalargtsel(PG_FUNCTION_ARGS)
|
|||||||
int varRelid = PG_GETARG_INT32(3);
|
int varRelid = PG_GETARG_INT32(3);
|
||||||
Var *var;
|
Var *var;
|
||||||
Node *other;
|
Node *other;
|
||||||
|
Datum constval;
|
||||||
|
Oid consttype;
|
||||||
bool varonleft;
|
bool varonleft;
|
||||||
bool isgt;
|
bool isgt;
|
||||||
double selec;
|
double selec;
|
||||||
@ -760,6 +767,22 @@ scalargtsel(PG_FUNCTION_ARGS)
|
|||||||
&var, &other, &varonleft))
|
&var, &other, &varonleft))
|
||||||
PG_RETURN_FLOAT8(DEFAULT_INEQ_SEL);
|
PG_RETURN_FLOAT8(DEFAULT_INEQ_SEL);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Can't do anything useful if the something is not a constant,
|
||||||
|
* either.
|
||||||
|
*/
|
||||||
|
if (!IsA(other, Const))
|
||||||
|
PG_RETURN_FLOAT8(DEFAULT_INEQ_SEL);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If the constant is NULL, assume operator is strict and return zero,
|
||||||
|
* ie, operator will never return TRUE.
|
||||||
|
*/
|
||||||
|
if (((Const *) other)->constisnull)
|
||||||
|
PG_RETURN_FLOAT8(0.0);
|
||||||
|
constval = ((Const *) other)->constvalue;
|
||||||
|
consttype = ((Const *) other)->consttype;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Force the var to be on the left to simplify logic in scalarineqsel.
|
* Force the var to be on the left to simplify logic in scalarineqsel.
|
||||||
*/
|
*/
|
||||||
@ -780,7 +803,7 @@ scalargtsel(PG_FUNCTION_ARGS)
|
|||||||
isgt = false;
|
isgt = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
selec = scalarineqsel(root, operator, isgt, var, other);
|
selec = scalarineqsel(root, operator, isgt, var, constval, consttype);
|
||||||
|
|
||||||
PG_RETURN_FLOAT8((float8) selec);
|
PG_RETURN_FLOAT8((float8) selec);
|
||||||
}
|
}
|
||||||
@ -1696,6 +1719,229 @@ icnlikejoinsel(PG_FUNCTION_ARGS)
|
|||||||
PG_RETURN_FLOAT8(result);
|
PG_RETURN_FLOAT8(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* mergejoinscansel - Scan selectivity of merge join.
|
||||||
|
*
|
||||||
|
* A merge join will stop as soon as it exhausts either input stream.
|
||||||
|
* Therefore, if we can estimate the ranges of both input variables,
|
||||||
|
* we can estimate how much of the input will actually be read. This
|
||||||
|
* can have a considerable impact on the cost when using indexscans.
|
||||||
|
*
|
||||||
|
* clause should be a clause already known to be mergejoinable.
|
||||||
|
*
|
||||||
|
* *leftscan is set to the fraction of the left-hand variable expected
|
||||||
|
* to be scanned (0 to 1), and similarly *rightscan for the right-hand
|
||||||
|
* variable.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
mergejoinscansel(Query *root, Node *clause,
|
||||||
|
Selectivity *leftscan,
|
||||||
|
Selectivity *rightscan)
|
||||||
|
{
|
||||||
|
Var *left,
|
||||||
|
*right;
|
||||||
|
Oid opno,
|
||||||
|
lsortop,
|
||||||
|
rsortop,
|
||||||
|
ltop,
|
||||||
|
gtop,
|
||||||
|
revltop;
|
||||||
|
Datum leftmax,
|
||||||
|
rightmax;
|
||||||
|
double selec;
|
||||||
|
|
||||||
|
/* Set default results if we can't figure anything out. */
|
||||||
|
*leftscan = *rightscan = 1.0;
|
||||||
|
|
||||||
|
/* Deconstruct the merge clause */
|
||||||
|
if (!is_opclause(clause))
|
||||||
|
return; /* shouldn't happen */
|
||||||
|
opno = ((Oper *) ((Expr *) clause)->oper)->opno;
|
||||||
|
left = get_leftop((Expr *) clause);
|
||||||
|
right = get_rightop((Expr *) clause);
|
||||||
|
if (!right)
|
||||||
|
return; /* shouldn't happen */
|
||||||
|
|
||||||
|
/* Can't do anything if inputs are not Vars */
|
||||||
|
if (!IsA(left, Var) ||!IsA(right, Var))
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* Verify mergejoinability and get left and right "<" operators */
|
||||||
|
if (!op_mergejoinable(opno,
|
||||||
|
left->vartype,
|
||||||
|
right->vartype,
|
||||||
|
&lsortop,
|
||||||
|
&rsortop))
|
||||||
|
return; /* shouldn't happen */
|
||||||
|
|
||||||
|
/* Try to get maximum values of both vars */
|
||||||
|
if (!get_var_maximum(root, left, lsortop, &leftmax))
|
||||||
|
return; /* no max available from stats */
|
||||||
|
|
||||||
|
if (!get_var_maximum(root, right, rsortop, &rightmax))
|
||||||
|
return; /* no max available from stats */
|
||||||
|
|
||||||
|
/* Look up the "left < right" and "left > right" operators */
|
||||||
|
op_mergejoin_crossops(opno, <op, >op, NULL, NULL);
|
||||||
|
|
||||||
|
/* Look up the "right < left" operator */
|
||||||
|
revltop = get_commutator(gtop);
|
||||||
|
if (!OidIsValid(revltop))
|
||||||
|
return; /* shouldn't happen */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Now, the fraction of the left variable that will be scanned is the
|
||||||
|
* fraction that's <= the right-side maximum value. But only believe
|
||||||
|
* non-default estimates, else stick with our 1.0.
|
||||||
|
*/
|
||||||
|
selec = scalarineqsel(root, ltop, false, left,
|
||||||
|
rightmax, right->vartype);
|
||||||
|
if (selec != DEFAULT_INEQ_SEL)
|
||||||
|
*leftscan = selec;
|
||||||
|
|
||||||
|
/* And similarly for the right variable. */
|
||||||
|
selec = scalarineqsel(root, revltop, false, right,
|
||||||
|
leftmax, left->vartype);
|
||||||
|
if (selec != DEFAULT_INEQ_SEL)
|
||||||
|
*rightscan = selec;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Only one of the two fractions can really be less than 1.0; believe
|
||||||
|
* the smaller estimate and reset the other one to exactly 1.0.
|
||||||
|
*/
|
||||||
|
if (*leftscan > *rightscan)
|
||||||
|
*leftscan = 1.0;
|
||||||
|
else
|
||||||
|
*rightscan = 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* get_var_maximum
|
||||||
|
* Estimate the maximum value of the specified variable.
|
||||||
|
* If successful, store value in *max and return TRUE.
|
||||||
|
* If no data available, return FALSE.
|
||||||
|
*
|
||||||
|
* sortop is the "<" comparison operator to use. (To extract the
|
||||||
|
* minimum instead of the maximum, just pass the ">" operator instead.)
|
||||||
|
*/
|
||||||
|
static bool
|
||||||
|
get_var_maximum(Query *root, Var *var, Oid sortop, Datum *max)
|
||||||
|
{
|
||||||
|
Datum tmax = 0;
|
||||||
|
bool have_max = false;
|
||||||
|
Oid relid;
|
||||||
|
HeapTuple statsTuple;
|
||||||
|
Form_pg_statistic stats;
|
||||||
|
int16 typLen;
|
||||||
|
bool typByVal;
|
||||||
|
Datum *values;
|
||||||
|
int nvalues;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
relid = getrelid(var->varno, root->rtable);
|
||||||
|
if (relid == InvalidOid)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
/* get stats for the attribute */
|
||||||
|
statsTuple = SearchSysCache(STATRELATT,
|
||||||
|
ObjectIdGetDatum(relid),
|
||||||
|
Int16GetDatum(var->varattno),
|
||||||
|
0, 0);
|
||||||
|
if (!HeapTupleIsValid(statsTuple))
|
||||||
|
{
|
||||||
|
/* no stats available, so default result */
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
stats = (Form_pg_statistic) GETSTRUCT(statsTuple);
|
||||||
|
|
||||||
|
get_typlenbyval(var->vartype, &typLen, &typByVal);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If there is a histogram, grab the last or first value as appropriate.
|
||||||
|
*
|
||||||
|
* If there is a histogram that is sorted with some other operator
|
||||||
|
* than the one we want, fail --- this suggests that there is data
|
||||||
|
* we can't use.
|
||||||
|
*/
|
||||||
|
if (get_attstatsslot(statsTuple, var->vartype, var->vartypmod,
|
||||||
|
STATISTIC_KIND_HISTOGRAM, sortop,
|
||||||
|
&values, &nvalues,
|
||||||
|
NULL, NULL))
|
||||||
|
{
|
||||||
|
if (nvalues > 0)
|
||||||
|
{
|
||||||
|
tmax = datumCopy(values[nvalues-1], typByVal, typLen);
|
||||||
|
have_max = true;
|
||||||
|
}
|
||||||
|
free_attstatsslot(var->vartype, values, nvalues, NULL, 0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Oid rsortop = get_commutator(sortop);
|
||||||
|
|
||||||
|
if (OidIsValid(rsortop) &&
|
||||||
|
get_attstatsslot(statsTuple, var->vartype, var->vartypmod,
|
||||||
|
STATISTIC_KIND_HISTOGRAM, rsortop,
|
||||||
|
&values, &nvalues,
|
||||||
|
NULL, NULL))
|
||||||
|
{
|
||||||
|
if (nvalues > 0)
|
||||||
|
{
|
||||||
|
tmax = datumCopy(values[0], typByVal, typLen);
|
||||||
|
have_max = true;
|
||||||
|
}
|
||||||
|
free_attstatsslot(var->vartype, values, nvalues, NULL, 0);
|
||||||
|
}
|
||||||
|
else if (get_attstatsslot(statsTuple, var->vartype, var->vartypmod,
|
||||||
|
STATISTIC_KIND_HISTOGRAM, InvalidOid,
|
||||||
|
&values, &nvalues,
|
||||||
|
NULL, NULL))
|
||||||
|
{
|
||||||
|
free_attstatsslot(var->vartype, values, nvalues, NULL, 0);
|
||||||
|
ReleaseSysCache(statsTuple);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If we have most-common-values info, look for a large MCV. This
|
||||||
|
* is needed even if we also have a histogram, since the histogram
|
||||||
|
* excludes the MCVs. However, usually the MCVs will not be the
|
||||||
|
* extreme values, so avoid unnecessary data copying.
|
||||||
|
*/
|
||||||
|
if (get_attstatsslot(statsTuple, var->vartype, var->vartypmod,
|
||||||
|
STATISTIC_KIND_MCV, InvalidOid,
|
||||||
|
&values, &nvalues,
|
||||||
|
NULL, NULL))
|
||||||
|
{
|
||||||
|
bool large_mcv = false;
|
||||||
|
FmgrInfo opproc;
|
||||||
|
|
||||||
|
fmgr_info(get_opcode(sortop), &opproc);
|
||||||
|
|
||||||
|
for (i = 0; i < nvalues; i++)
|
||||||
|
{
|
||||||
|
if (!have_max)
|
||||||
|
{
|
||||||
|
tmax = values[i];
|
||||||
|
large_mcv = have_max = true;
|
||||||
|
}
|
||||||
|
else if (DatumGetBool(FunctionCall2(&opproc, tmax, values[i])))
|
||||||
|
{
|
||||||
|
tmax = values[i];
|
||||||
|
large_mcv = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (large_mcv)
|
||||||
|
tmax = datumCopy(tmax, typByVal, typLen);
|
||||||
|
free_attstatsslot(var->vartype, values, nvalues, NULL, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
ReleaseSysCache(statsTuple);
|
||||||
|
|
||||||
|
*max = tmax;
|
||||||
|
return have_max;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* convert_to_scalar
|
* convert_to_scalar
|
||||||
|
72
src/backend/utils/cache/lsyscache.c
vendored
72
src/backend/utils/cache/lsyscache.c
vendored
@ -7,7 +7,7 @@
|
|||||||
* Portions Copyright (c) 1994, Regents of the University of California
|
* Portions Copyright (c) 1994, Regents of the University of California
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $Header: /cvsroot/pgsql/src/backend/utils/cache/lsyscache.c,v 1.59 2001/10/25 05:49:46 momjian Exp $
|
* $Header: /cvsroot/pgsql/src/backend/utils/cache/lsyscache.c,v 1.60 2002/03/01 04:09:26 tgl Exp $
|
||||||
*
|
*
|
||||||
* NOTES
|
* NOTES
|
||||||
* Eventually, the index information should go through here, too.
|
* Eventually, the index information should go through here, too.
|
||||||
@ -369,6 +369,76 @@ op_mergejoinable(Oid opno, Oid ltype, Oid rtype, Oid *leftOp, Oid *rightOp)
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* op_mergejoin_crossops
|
||||||
|
*
|
||||||
|
* Returns the cross-type comparison operators (ltype "<" rtype and
|
||||||
|
* ltype ">" rtype) for an operator previously determined to be
|
||||||
|
* mergejoinable. Optionally, fetches the regproc ids of these
|
||||||
|
* operators, as well as their operator OIDs.
|
||||||
|
*
|
||||||
|
* Raises error if operators cannot be found. Assuming that the operator
|
||||||
|
* had indeed been marked mergejoinable, this indicates that whoever marked
|
||||||
|
* it so was mistaken.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
op_mergejoin_crossops(Oid opno, Oid *ltop, Oid *gtop,
|
||||||
|
RegProcedure *ltproc, RegProcedure *gtproc)
|
||||||
|
{
|
||||||
|
HeapTuple tp;
|
||||||
|
Form_pg_operator optup;
|
||||||
|
Oid oprleft,
|
||||||
|
oprright;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get the declared left and right operand types of the operator.
|
||||||
|
*/
|
||||||
|
tp = SearchSysCache(OPEROID,
|
||||||
|
ObjectIdGetDatum(opno),
|
||||||
|
0, 0, 0);
|
||||||
|
if (!HeapTupleIsValid(tp)) /* shouldn't happen */
|
||||||
|
elog(ERROR, "op_mergejoin_crossops: operator %u not found", opno);
|
||||||
|
optup = (Form_pg_operator) GETSTRUCT(tp);
|
||||||
|
oprleft = optup->oprleft;
|
||||||
|
oprright = optup->oprright;
|
||||||
|
ReleaseSysCache(tp);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Look up the "<" operator with the same input types. If there isn't
|
||||||
|
* one, whoever marked the "=" operator mergejoinable was a loser.
|
||||||
|
*/
|
||||||
|
tp = SearchSysCache(OPERNAME,
|
||||||
|
PointerGetDatum("<"),
|
||||||
|
ObjectIdGetDatum(oprleft),
|
||||||
|
ObjectIdGetDatum(oprright),
|
||||||
|
CharGetDatum('b'));
|
||||||
|
if (!HeapTupleIsValid(tp))
|
||||||
|
elog(ERROR, "op_mergejoin_crossops: mergejoin operator %u has no matching < operator",
|
||||||
|
opno);
|
||||||
|
optup = (Form_pg_operator) GETSTRUCT(tp);
|
||||||
|
*ltop = tp->t_data->t_oid;
|
||||||
|
if (ltproc)
|
||||||
|
*ltproc = optup->oprcode;
|
||||||
|
ReleaseSysCache(tp);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* And the same for the ">" operator.
|
||||||
|
*/
|
||||||
|
tp = SearchSysCache(OPERNAME,
|
||||||
|
PointerGetDatum(">"),
|
||||||
|
ObjectIdGetDatum(oprleft),
|
||||||
|
ObjectIdGetDatum(oprright),
|
||||||
|
CharGetDatum('b'));
|
||||||
|
if (!HeapTupleIsValid(tp))
|
||||||
|
elog(ERROR, "op_mergejoin_crossops: mergejoin operator %u has no matching > operator",
|
||||||
|
opno);
|
||||||
|
optup = (Form_pg_operator) GETSTRUCT(tp);
|
||||||
|
*gtop = tp->t_data->t_oid;
|
||||||
|
if (gtproc)
|
||||||
|
*gtproc = optup->oprcode;
|
||||||
|
ReleaseSysCache(tp);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* op_hashjoinable
|
* op_hashjoinable
|
||||||
*
|
*
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
* Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
|
* Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
|
||||||
* Portions Copyright (c) 1994, Regents of the University of California
|
* Portions Copyright (c) 1994, Regents of the University of California
|
||||||
*
|
*
|
||||||
* $Id: lsyscache.h,v 1.39 2001/11/05 17:46:36 momjian Exp $
|
* $Id: lsyscache.h,v 1.40 2002/03/01 04:09:28 tgl Exp $
|
||||||
*
|
*
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
@ -29,6 +29,8 @@ extern RegProcedure get_opcode(Oid opno);
|
|||||||
extern char *get_opname(Oid opno);
|
extern char *get_opname(Oid opno);
|
||||||
extern bool op_mergejoinable(Oid opno, Oid ltype, Oid rtype,
|
extern bool op_mergejoinable(Oid opno, Oid ltype, Oid rtype,
|
||||||
Oid *leftOp, Oid *rightOp);
|
Oid *leftOp, Oid *rightOp);
|
||||||
|
extern void op_mergejoin_crossops(Oid opno, Oid *ltop, Oid *gtop,
|
||||||
|
RegProcedure *ltproc, RegProcedure *gtproc);
|
||||||
extern Oid op_hashjoinable(Oid opno, Oid ltype, Oid rtype);
|
extern Oid op_hashjoinable(Oid opno, Oid ltype, Oid rtype);
|
||||||
extern bool op_iscachable(Oid opno);
|
extern bool op_iscachable(Oid opno);
|
||||||
extern Oid get_commutator(Oid opno);
|
extern Oid get_commutator(Oid opno);
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
* Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
|
* Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
|
||||||
* Portions Copyright (c) 1994, Regents of the University of California
|
* Portions Copyright (c) 1994, Regents of the University of California
|
||||||
*
|
*
|
||||||
* $Id: selfuncs.h,v 1.4 2001/11/05 17:46:36 momjian Exp $
|
* $Id: selfuncs.h,v 1.5 2002/03/01 04:09:28 tgl Exp $
|
||||||
*
|
*
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
@ -66,8 +66,12 @@ extern Datum icregexnejoinsel(PG_FUNCTION_ARGS);
|
|||||||
extern Datum nlikejoinsel(PG_FUNCTION_ARGS);
|
extern Datum nlikejoinsel(PG_FUNCTION_ARGS);
|
||||||
extern Datum icnlikejoinsel(PG_FUNCTION_ARGS);
|
extern Datum icnlikejoinsel(PG_FUNCTION_ARGS);
|
||||||
|
|
||||||
Selectivity booltestsel(Query *root, BooleanTest *clause, int varRelid);
|
extern Selectivity booltestsel(Query *root, BooleanTest *clause, int varRelid);
|
||||||
Selectivity nulltestsel(Query *root, NullTest *clause, int varRelid);
|
extern Selectivity nulltestsel(Query *root, NullTest *clause, int varRelid);
|
||||||
|
|
||||||
|
extern void mergejoinscansel(Query *root, Node *clause,
|
||||||
|
Selectivity *leftscan,
|
||||||
|
Selectivity *rightscan);
|
||||||
|
|
||||||
extern Datum btcostestimate(PG_FUNCTION_ARGS);
|
extern Datum btcostestimate(PG_FUNCTION_ARGS);
|
||||||
extern Datum rtcostestimate(PG_FUNCTION_ARGS);
|
extern Datum rtcostestimate(PG_FUNCTION_ARGS);
|
||||||
|
Reference in New Issue
Block a user