mirror of
https://git.postgresql.org/git/postgresql.git
synced 2024-12-27 08:39:28 +08:00
Teach tid-scan code to make use of "ctid = ANY (array)" clauses, so that
"ctid IN (list)" will still work after we convert IN to ScalarArrayOpExpr. Make some minor efficiency improvements while at it, such as ensuring that multiple TIDs are fetched in physical heap order. And fix EXPLAIN so that it shows what's really going on for a TID scan.
This commit is contained in:
parent
a66e2c8885
commit
da27c0a1ef
@ -7,7 +7,7 @@
|
||||
* Portions Copyright (c) 1994-5, Regents of the University of California
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/commands/explain.c,v 1.140 2005/11/22 18:17:09 momjian Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/commands/explain.c,v 1.141 2005/11/26 22:14:56 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -724,7 +724,6 @@ explain_outNode(StringInfo str,
|
||||
str, indent, es);
|
||||
/* FALL THRU */
|
||||
case T_SeqScan:
|
||||
case T_TidScan:
|
||||
case T_SubqueryScan:
|
||||
case T_FunctionScan:
|
||||
show_scan_qual(plan->qual,
|
||||
@ -733,6 +732,28 @@ explain_outNode(StringInfo str,
|
||||
outer_plan,
|
||||
str, indent, es);
|
||||
break;
|
||||
case T_TidScan:
|
||||
{
|
||||
/*
|
||||
* The tidquals list has OR semantics, so be sure to show it
|
||||
* as an OR condition.
|
||||
*/
|
||||
List *tidquals = ((TidScan *) plan)->tidquals;
|
||||
|
||||
if (list_length(tidquals) > 1)
|
||||
tidquals = list_make1(make_orclause(tidquals));
|
||||
show_scan_qual(tidquals,
|
||||
"TID Cond",
|
||||
((Scan *) plan)->scanrelid,
|
||||
outer_plan,
|
||||
str, indent, es);
|
||||
show_scan_qual(plan->qual,
|
||||
"Filter",
|
||||
((Scan *) plan)->scanrelid,
|
||||
outer_plan,
|
||||
str, indent, es);
|
||||
}
|
||||
break;
|
||||
case T_NestLoop:
|
||||
show_upper_qual(((NestLoop *) plan)->join.joinqual,
|
||||
"Join Filter",
|
||||
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/executor/nodeTidscan.c,v 1.44 2005/11/25 04:24:48 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/executor/nodeTidscan.c,v 1.45 2005/11/26 22:14:56 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -24,47 +24,156 @@
|
||||
*/
|
||||
#include "postgres.h"
|
||||
|
||||
#include "access/heapam.h"
|
||||
#include "catalog/pg_type.h"
|
||||
#include "executor/execdebug.h"
|
||||
#include "executor/nodeTidscan.h"
|
||||
#include "access/heapam.h"
|
||||
#include "optimizer/clauses.h"
|
||||
#include "parser/parsetree.h"
|
||||
#include "utils/array.h"
|
||||
|
||||
|
||||
#define IsCTIDVar(node) \
|
||||
((node) != NULL && \
|
||||
IsA((node), Var) && \
|
||||
((Var *) (node))->varattno == SelfItemPointerAttributeNumber && \
|
||||
((Var *) (node))->varlevelsup == 0)
|
||||
|
||||
static void TidListCreate(TidScanState *tidstate);
|
||||
static int itemptr_comparator(const void *a, const void *b);
|
||||
static TupleTableSlot *TidNext(TidScanState *node);
|
||||
|
||||
|
||||
/*
|
||||
* Compute the list of TIDs to be visited, by evaluating the expressions
|
||||
* for them.
|
||||
*
|
||||
* (The result is actually an array, not a list.)
|
||||
*/
|
||||
static void
|
||||
TidListCreate(TidScanState *tidstate)
|
||||
{
|
||||
List *evalList = tidstate->tss_tideval;
|
||||
List *evalList = tidstate->tss_tidquals;
|
||||
ExprContext *econtext = tidstate->ss.ps.ps_ExprContext;
|
||||
ItemPointerData *tidList;
|
||||
int numTids = 0;
|
||||
int numAllocTids;
|
||||
int numTids;
|
||||
ListCell *l;
|
||||
|
||||
/*
|
||||
* We initialize the array with enough slots for the case that all
|
||||
* quals are simple OpExprs. If there's any ScalarArrayOpExprs,
|
||||
* we may have to enlarge the array.
|
||||
*/
|
||||
numAllocTids = list_length(evalList);
|
||||
tidList = (ItemPointerData *)
|
||||
palloc(list_length(tidstate->tss_tideval) * sizeof(ItemPointerData));
|
||||
palloc(numAllocTids * sizeof(ItemPointerData));
|
||||
numTids = 0;
|
||||
|
||||
foreach(l, evalList)
|
||||
{
|
||||
ExprState *exstate = (ExprState *) lfirst(l);
|
||||
Expr *expr = exstate->expr;
|
||||
ItemPointer itemptr;
|
||||
bool isNull;
|
||||
|
||||
itemptr = (ItemPointer)
|
||||
DatumGetPointer(ExecEvalExprSwitchContext(lfirst(l),
|
||||
econtext,
|
||||
&isNull,
|
||||
NULL));
|
||||
if (!isNull && itemptr && ItemPointerIsValid(itemptr))
|
||||
if (is_opclause(expr))
|
||||
{
|
||||
tidList[numTids] = *itemptr;
|
||||
numTids++;
|
||||
FuncExprState *fexstate = (FuncExprState *) exstate;
|
||||
Node *arg1;
|
||||
Node *arg2;
|
||||
|
||||
arg1 = get_leftop(expr);
|
||||
arg2 = get_rightop(expr);
|
||||
if (IsCTIDVar(arg1))
|
||||
exstate = (ExprState *) lsecond(fexstate->args);
|
||||
else if (IsCTIDVar(arg2))
|
||||
exstate = (ExprState *) linitial(fexstate->args);
|
||||
else
|
||||
elog(ERROR, "could not identify CTID variable");
|
||||
|
||||
itemptr = (ItemPointer)
|
||||
DatumGetPointer(ExecEvalExprSwitchContext(exstate,
|
||||
econtext,
|
||||
&isNull,
|
||||
NULL));
|
||||
if (!isNull && ItemPointerIsValid(itemptr))
|
||||
{
|
||||
if (numTids >= numAllocTids)
|
||||
{
|
||||
numAllocTids *= 2;
|
||||
tidList = (ItemPointerData *)
|
||||
repalloc(tidList,
|
||||
numAllocTids * sizeof(ItemPointerData));
|
||||
}
|
||||
tidList[numTids++] = *itemptr;
|
||||
}
|
||||
}
|
||||
else if (expr && IsA(expr, ScalarArrayOpExpr))
|
||||
{
|
||||
ScalarArrayOpExprState *saexstate = (ScalarArrayOpExprState *) exstate;
|
||||
Datum arraydatum;
|
||||
ArrayType *itemarray;
|
||||
Datum *ipdatums;
|
||||
bool *ipnulls;
|
||||
int ndatums;
|
||||
int i;
|
||||
|
||||
exstate = (ExprState *) lsecond(saexstate->fxprstate.args);
|
||||
arraydatum = ExecEvalExprSwitchContext(exstate,
|
||||
econtext,
|
||||
&isNull,
|
||||
NULL);
|
||||
if (isNull)
|
||||
continue;
|
||||
itemarray = DatumGetArrayTypeP(arraydatum);
|
||||
deconstruct_array(itemarray,
|
||||
TIDOID, SizeOfIptrData, false, 's',
|
||||
&ipdatums, &ipnulls, &ndatums);
|
||||
if (numTids + ndatums > numAllocTids)
|
||||
{
|
||||
numAllocTids = numTids + ndatums;
|
||||
tidList = (ItemPointerData *)
|
||||
repalloc(tidList,
|
||||
numAllocTids * sizeof(ItemPointerData));
|
||||
}
|
||||
for (i = 0; i < ndatums; i++)
|
||||
{
|
||||
if (!ipnulls[i])
|
||||
{
|
||||
itemptr = (ItemPointer) DatumGetPointer(ipdatums[i]);
|
||||
if (ItemPointerIsValid(itemptr))
|
||||
tidList[numTids++] = *itemptr;
|
||||
}
|
||||
}
|
||||
pfree(ipdatums);
|
||||
pfree(ipnulls);
|
||||
}
|
||||
else
|
||||
elog(ERROR, "could not identify CTID expression");
|
||||
}
|
||||
|
||||
/*
|
||||
* Sort the array of TIDs into order, and eliminate duplicates.
|
||||
* Eliminating duplicates is necessary since we want OR semantics
|
||||
* across the list. Sorting makes it easier to detect duplicates,
|
||||
* and as a bonus ensures that we will visit the heap in the most
|
||||
* efficient way.
|
||||
*/
|
||||
if (numTids > 1)
|
||||
{
|
||||
int lastTid;
|
||||
int i;
|
||||
|
||||
qsort((void *) tidList, numTids, sizeof(ItemPointerData),
|
||||
itemptr_comparator);
|
||||
lastTid = 0;
|
||||
for (i = 1; i < numTids; i++)
|
||||
{
|
||||
if (!ItemPointerEquals(&tidList[lastTid], &tidList[i]))
|
||||
tidList[++lastTid] = tidList[i];
|
||||
}
|
||||
numTids = lastTid + 1;
|
||||
}
|
||||
|
||||
tidstate->tss_TidList = tidList;
|
||||
@ -72,6 +181,30 @@ TidListCreate(TidScanState *tidstate)
|
||||
tidstate->tss_TidPtr = -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* qsort comparator for ItemPointerData items
|
||||
*/
|
||||
static int
|
||||
itemptr_comparator(const void *a, const void *b)
|
||||
{
|
||||
const ItemPointerData *ipa = (const ItemPointerData *) a;
|
||||
const ItemPointerData *ipb = (const ItemPointerData *) b;
|
||||
BlockNumber ba = ItemPointerGetBlockNumber(ipa);
|
||||
BlockNumber bb = ItemPointerGetBlockNumber(ipb);
|
||||
OffsetNumber oa = ItemPointerGetOffsetNumber(ipa);
|
||||
OffsetNumber ob = ItemPointerGetOffsetNumber(ipb);
|
||||
|
||||
if (ba < bb)
|
||||
return -1;
|
||||
if (ba > bb)
|
||||
return 1;
|
||||
if (oa < ob)
|
||||
return -1;
|
||||
if (oa > ob)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
* TidNext
|
||||
*
|
||||
@ -94,7 +227,6 @@ TidNext(TidScanState *node)
|
||||
ItemPointerData *tidList;
|
||||
int numTids;
|
||||
bool bBackward;
|
||||
int tidNumber;
|
||||
|
||||
/*
|
||||
* extract necessary information from tid scan node
|
||||
@ -143,38 +275,35 @@ TidNext(TidScanState *node)
|
||||
tuple = &(node->tss_htup);
|
||||
|
||||
/*
|
||||
* ok, now that we have what we need, fetch an tid tuple. if scanning this
|
||||
* tid succeeded then return the appropriate heap tuple.. else return
|
||||
* NULL.
|
||||
* Initialize or advance scan position, depending on direction.
|
||||
*/
|
||||
bBackward = ScanDirectionIsBackward(direction);
|
||||
if (bBackward)
|
||||
{
|
||||
tidNumber = numTids - node->tss_TidPtr - 1;
|
||||
if (tidNumber < 0)
|
||||
if (node->tss_TidPtr < 0)
|
||||
{
|
||||
tidNumber = 0;
|
||||
/* initialize for backward scan */
|
||||
node->tss_TidPtr = numTids - 1;
|
||||
}
|
||||
else
|
||||
node->tss_TidPtr--;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((tidNumber = node->tss_TidPtr) < 0)
|
||||
if (node->tss_TidPtr < 0)
|
||||
{
|
||||
tidNumber = 0;
|
||||
/* initialize for forward scan */
|
||||
node->tss_TidPtr = 0;
|
||||
}
|
||||
else
|
||||
node->tss_TidPtr++;
|
||||
}
|
||||
while (tidNumber < numTids)
|
||||
{
|
||||
bool slot_is_valid = false;
|
||||
|
||||
while (node->tss_TidPtr >= 0 && node->tss_TidPtr < numTids)
|
||||
{
|
||||
tuple->t_self = tidList[node->tss_TidPtr];
|
||||
if (heap_fetch(heapRelation, snapshot, tuple, &buffer, false, NULL))
|
||||
{
|
||||
bool prev_matches = false;
|
||||
int prev_tid;
|
||||
|
||||
/*
|
||||
* store the scanned tuple in the scan tuple slot of the scan
|
||||
* state. Eventually we will only do this and not return a tuple.
|
||||
@ -193,31 +322,13 @@ TidNext(TidScanState *node)
|
||||
*/
|
||||
ReleaseBuffer(buffer);
|
||||
|
||||
/*
|
||||
* We must check to see if the current tuple would have been
|
||||
* matched by an earlier tid, so we don't double report it.
|
||||
*/
|
||||
for (prev_tid = 0; prev_tid < node->tss_TidPtr;
|
||||
prev_tid++)
|
||||
{
|
||||
if (ItemPointerEquals(&tidList[prev_tid], &tuple->t_self))
|
||||
{
|
||||
prev_matches = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!prev_matches)
|
||||
slot_is_valid = true;
|
||||
else
|
||||
ExecClearTuple(slot);
|
||||
return slot;
|
||||
}
|
||||
tidNumber++;
|
||||
/* Bad TID or failed snapshot qual; try next */
|
||||
if (bBackward)
|
||||
node->tss_TidPtr--;
|
||||
else
|
||||
node->tss_TidPtr++;
|
||||
if (slot_is_valid)
|
||||
return slot;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -242,8 +353,7 @@ TidNext(TidScanState *node)
|
||||
* Initial States:
|
||||
* -- the relation indicated is opened for scanning so that the
|
||||
* "cursor" is positioned before the first qualifying tuple.
|
||||
* -- tidPtr points to the first tid.
|
||||
* -- state variable ruleFlag = nil.
|
||||
* -- tidPtr is -1.
|
||||
* ----------------------------------------------------------------
|
||||
*/
|
||||
TupleTableSlot *
|
||||
@ -362,7 +472,6 @@ TidScanState *
|
||||
ExecInitTidScan(TidScan *node, EState *estate)
|
||||
{
|
||||
TidScanState *tidstate;
|
||||
List *rangeTable;
|
||||
RangeTblEntry *rtentry;
|
||||
Oid relid;
|
||||
Oid reloid;
|
||||
@ -392,8 +501,8 @@ ExecInitTidScan(TidScan *node, EState *estate)
|
||||
ExecInitExpr((Expr *) node->scan.plan.qual,
|
||||
(PlanState *) tidstate);
|
||||
|
||||
tidstate->tss_tideval = (List *)
|
||||
ExecInitExpr((Expr *) node->tideval,
|
||||
tidstate->tss_tidquals = (List *)
|
||||
ExecInitExpr((Expr *) node->tidquals,
|
||||
(PlanState *) tidstate);
|
||||
|
||||
#define TIDSCAN_NSLOTS 2
|
||||
@ -411,19 +520,13 @@ ExecInitTidScan(TidScan *node, EState *estate)
|
||||
tidstate->tss_NumTids = 0;
|
||||
tidstate->tss_TidPtr = -1;
|
||||
|
||||
/*
|
||||
* get the range table and direction information from the execution state
|
||||
* (these are needed to open the relations).
|
||||
*/
|
||||
rangeTable = estate->es_range_table;
|
||||
|
||||
/*
|
||||
* open the base relation
|
||||
*
|
||||
* We acquire AccessShareLock for the duration of the scan.
|
||||
*/
|
||||
relid = node->scan.scanrelid;
|
||||
rtentry = rt_fetch(relid, rangeTable);
|
||||
rtentry = rt_fetch(relid, estate->es_range_table);
|
||||
reloid = rtentry->relid;
|
||||
|
||||
currentRelation = heap_open(reloid, AccessShareLock);
|
||||
|
@ -15,7 +15,7 @@
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.321 2005/11/22 18:17:11 momjian Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.322 2005/11/26 22:14:56 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -325,7 +325,7 @@ _copyTidScan(TidScan *from)
|
||||
/*
|
||||
* copy remainder of node
|
||||
*/
|
||||
COPY_NODE_FIELD(tideval);
|
||||
COPY_NODE_FIELD(tidquals);
|
||||
|
||||
return newnode;
|
||||
}
|
||||
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.262 2005/11/14 23:54:15 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.263 2005/11/26 22:14:56 tgl Exp $
|
||||
*
|
||||
* NOTES
|
||||
* Every node type that can appear in stored rules' parsetrees *must*
|
||||
@ -390,7 +390,7 @@ _outTidScan(StringInfo str, TidScan *node)
|
||||
|
||||
_outScanInfo(str, (Scan *) node);
|
||||
|
||||
WRITE_NODE_FIELD(tideval);
|
||||
WRITE_NODE_FIELD(tidquals);
|
||||
}
|
||||
|
||||
static void
|
||||
@ -1079,7 +1079,7 @@ _outTidPath(StringInfo str, TidPath *node)
|
||||
|
||||
_outPathInfo(str, (Path *) node);
|
||||
|
||||
WRITE_NODE_FIELD(tideval);
|
||||
WRITE_NODE_FIELD(tidquals);
|
||||
}
|
||||
|
||||
static void
|
||||
|
@ -49,7 +49,7 @@
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/optimizer/path/costsize.c,v 1.150 2005/11/22 18:17:12 momjian Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/optimizer/path/costsize.c,v 1.151 2005/11/26 22:14:56 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -66,6 +66,7 @@
|
||||
#include "optimizer/pathnode.h"
|
||||
#include "optimizer/plancat.h"
|
||||
#include "parser/parsetree.h"
|
||||
#include "utils/array.h"
|
||||
#include "utils/selfuncs.h"
|
||||
#include "utils/lsyscache.h"
|
||||
#include "utils/syscache.h"
|
||||
@ -104,6 +105,7 @@ bool enable_hashjoin = true;
|
||||
|
||||
|
||||
static bool cost_qual_eval_walker(Node *node, QualCost *total);
|
||||
static int estimate_array_length(Node *arrayexpr);
|
||||
static Selectivity approx_selectivity(PlannerInfo *root, List *quals,
|
||||
JoinType jointype);
|
||||
static Selectivity join_in_selectivity(JoinPath *path, PlannerInfo *root);
|
||||
@ -617,12 +619,13 @@ cost_bitmap_or_node(BitmapOrPath *path, PlannerInfo *root)
|
||||
*/
|
||||
void
|
||||
cost_tidscan(Path *path, PlannerInfo *root,
|
||||
RelOptInfo *baserel, List *tideval)
|
||||
RelOptInfo *baserel, List *tidquals)
|
||||
{
|
||||
Cost startup_cost = 0;
|
||||
Cost run_cost = 0;
|
||||
Cost cpu_per_tuple;
|
||||
int ntuples = list_length(tideval);
|
||||
int ntuples;
|
||||
ListCell *l;
|
||||
|
||||
/* Should only be applied to base relations */
|
||||
Assert(baserel->relid > 0);
|
||||
@ -631,6 +634,25 @@ cost_tidscan(Path *path, PlannerInfo *root,
|
||||
if (!enable_tidscan)
|
||||
startup_cost += disable_cost;
|
||||
|
||||
/* Count how many tuples we expect to retrieve */
|
||||
ntuples = 0;
|
||||
foreach(l, tidquals)
|
||||
{
|
||||
if (IsA(lfirst(l), ScalarArrayOpExpr))
|
||||
{
|
||||
/* Each element of the array yields 1 tuple */
|
||||
ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) lfirst(l);
|
||||
Node *arraynode = (Node *) lsecond(saop->args);
|
||||
|
||||
ntuples += estimate_array_length(arraynode);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* It's just CTID = something, count 1 tuple */
|
||||
ntuples++;
|
||||
}
|
||||
}
|
||||
|
||||
/* disk costs --- assume each tuple on a different page */
|
||||
run_cost += random_page_cost * ntuples;
|
||||
|
||||
@ -643,6 +665,34 @@ cost_tidscan(Path *path, PlannerInfo *root,
|
||||
path->total_cost = startup_cost + run_cost;
|
||||
}
|
||||
|
||||
/*
|
||||
* Estimate number of elements in the array yielded by an expression.
|
||||
*/
|
||||
static int
|
||||
estimate_array_length(Node *arrayexpr)
|
||||
{
|
||||
if (arrayexpr && IsA(arrayexpr, Const))
|
||||
{
|
||||
Datum arraydatum = ((Const *) arrayexpr)->constvalue;
|
||||
bool arrayisnull = ((Const *) arrayexpr)->constisnull;
|
||||
ArrayType *arrayval;
|
||||
|
||||
if (arrayisnull)
|
||||
return 0;
|
||||
arrayval = DatumGetArrayTypeP(arraydatum);
|
||||
return ArrayGetNItems(ARR_NDIM(arrayval), ARR_DIMS(arrayval));
|
||||
}
|
||||
else if (arrayexpr && IsA(arrayexpr, ArrayExpr))
|
||||
{
|
||||
return list_length(((ArrayExpr *) arrayexpr)->elements);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* default guess */
|
||||
return 10;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* cost_subqueryscan
|
||||
* Determines and returns the cost of scanning a subquery RTE.
|
||||
@ -1549,8 +1599,15 @@ cost_qual_eval_walker(Node *node, QualCost *total)
|
||||
total->per_tuple += cpu_operator_cost;
|
||||
else if (IsA(node, ScalarArrayOpExpr))
|
||||
{
|
||||
/* should charge more than 1 op cost, but how many? */
|
||||
total->per_tuple += cpu_operator_cost * 10;
|
||||
/*
|
||||
* Estimate that the operator will be applied to about half of the
|
||||
* array elements before the answer is determined.
|
||||
*/
|
||||
ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) node;
|
||||
Node *arraynode = (Node *) lsecond(saop->args);
|
||||
|
||||
total->per_tuple +=
|
||||
cpu_operator_cost * estimate_array_length(arraynode) * 0.5;
|
||||
}
|
||||
else if (IsA(node, SubLink))
|
||||
{
|
||||
|
@ -6,8 +6,10 @@
|
||||
*
|
||||
* What we are looking for here is WHERE conditions of the form
|
||||
* "CTID = pseudoconstant", which can be implemented by just fetching
|
||||
* the tuple directly via heap_fetch(). We can also handle OR conditions
|
||||
* if each OR arm contains such a condition; in particular this allows
|
||||
* the tuple directly via heap_fetch(). We can also handle OR'd conditions
|
||||
* such as (CTID = const1) OR (CTID = const2), as well as ScalarArrayOpExpr
|
||||
* conditions of the form CTID = ANY(pseudoconstant_array). In particular
|
||||
* this allows
|
||||
* WHERE ctid IN (tid1, tid2, ...)
|
||||
*
|
||||
* There is currently no special support for joins involving CTID; in
|
||||
@ -22,7 +24,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/optimizer/path/tidpath.c,v 1.25 2005/10/15 02:49:20 momjian Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/optimizer/path/tidpath.c,v 1.26 2005/11/26 22:14:56 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -37,9 +39,10 @@
|
||||
#include "parser/parse_expr.h"
|
||||
|
||||
|
||||
static Node *IsTidEqualClause(int varno, OpExpr *node);
|
||||
static List *TidQualFromExpr(int varno, Node *expr);
|
||||
static List *TidQualFromRestrictinfo(int varno, List *restrictinfo);
|
||||
static bool IsTidEqualClause(OpExpr *node, int varno);
|
||||
static bool IsTidEqualAnyClause(ScalarArrayOpExpr *node, int varno);
|
||||
static List *TidQualFromExpr(Node *expr, int varno);
|
||||
static List *TidQualFromRestrictinfo(List *restrictinfo, int varno);
|
||||
|
||||
|
||||
/*
|
||||
@ -48,14 +51,12 @@ static List *TidQualFromRestrictinfo(int varno, List *restrictinfo);
|
||||
* or
|
||||
* pseudoconstant = CTID
|
||||
*
|
||||
* If it is, return the pseudoconstant subnode; if not, return NULL.
|
||||
*
|
||||
* We check that the CTID Var belongs to relation "varno". That is probably
|
||||
* redundant considering this is only applied to restriction clauses, but
|
||||
* let's be safe.
|
||||
*/
|
||||
static Node *
|
||||
IsTidEqualClause(int varno, OpExpr *node)
|
||||
static bool
|
||||
IsTidEqualClause(OpExpr *node, int varno)
|
||||
{
|
||||
Node *arg1,
|
||||
*arg2,
|
||||
@ -64,9 +65,9 @@ IsTidEqualClause(int varno, OpExpr *node)
|
||||
|
||||
/* Operator must be tideq */
|
||||
if (node->opno != TIDEqualOperator)
|
||||
return NULL;
|
||||
return false;
|
||||
if (list_length(node->args) != 2)
|
||||
return NULL;
|
||||
return false;
|
||||
arg1 = linitial(node->args);
|
||||
arg2 = lsecond(node->args);
|
||||
|
||||
@ -91,20 +92,61 @@ IsTidEqualClause(int varno, OpExpr *node)
|
||||
other = arg1;
|
||||
}
|
||||
if (!other)
|
||||
return NULL;
|
||||
return false;
|
||||
if (exprType(other) != TIDOID)
|
||||
return NULL; /* probably can't happen */
|
||||
return false; /* probably can't happen */
|
||||
|
||||
/* The other argument must be a pseudoconstant */
|
||||
if (!is_pseudo_constant_clause(other))
|
||||
return NULL;
|
||||
return false;
|
||||
|
||||
return other; /* success */
|
||||
return true; /* success */
|
||||
}
|
||||
|
||||
/*
|
||||
* Check to see if a clause is of the form
|
||||
* CTID = ANY (pseudoconstant_array)
|
||||
*/
|
||||
static bool
|
||||
IsTidEqualAnyClause(ScalarArrayOpExpr *node, int varno)
|
||||
{
|
||||
Node *arg1,
|
||||
*arg2;
|
||||
|
||||
/* Operator must be tideq */
|
||||
if (node->opno != TIDEqualOperator)
|
||||
return false;
|
||||
if (!node->useOr)
|
||||
return false;
|
||||
Assert(list_length(node->args) == 2);
|
||||
arg1 = linitial(node->args);
|
||||
arg2 = lsecond(node->args);
|
||||
|
||||
/* CTID must be first argument */
|
||||
if (arg1 && IsA(arg1, Var))
|
||||
{
|
||||
Var *var = (Var *) arg1;
|
||||
|
||||
if (var->varattno == SelfItemPointerAttributeNumber &&
|
||||
var->vartype == TIDOID &&
|
||||
var->varno == varno &&
|
||||
var->varlevelsup == 0)
|
||||
{
|
||||
/* The other argument must be a pseudoconstant */
|
||||
if (is_pseudo_constant_clause(arg2))
|
||||
return true; /* success */
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Extract a set of CTID conditions from the given qual expression
|
||||
*
|
||||
* Returns a List of CTID qual expressions (with implicit OR semantics
|
||||
* across the list), or NIL if there are no usable conditions.
|
||||
*
|
||||
* If the expression is an AND clause, we can use a CTID condition
|
||||
* from any sub-clause. If it is an OR clause, we must be able to
|
||||
* extract a CTID condition from every sub-clause, or we can't use it.
|
||||
@ -113,30 +155,30 @@ IsTidEqualClause(int varno, OpExpr *node)
|
||||
* sub-clauses, in which case we could try to pick the most efficient one.
|
||||
* In practice, such usage seems very unlikely, so we don't bother; we
|
||||
* just exit as soon as we find the first candidate.
|
||||
*
|
||||
* Returns a List of pseudoconstant TID expressions, or NIL if no match.
|
||||
* (Has to be a list for the OR case.)
|
||||
*/
|
||||
static List *
|
||||
TidQualFromExpr(int varno, Node *expr)
|
||||
TidQualFromExpr(Node *expr, int varno)
|
||||
{
|
||||
List *rlst = NIL,
|
||||
*frtn;
|
||||
List *rlst = NIL;
|
||||
ListCell *l;
|
||||
Node *rnode;
|
||||
|
||||
if (is_opclause(expr))
|
||||
{
|
||||
/* base case: check for tideq opclause */
|
||||
rnode = IsTidEqualClause(varno, (OpExpr *) expr);
|
||||
if (rnode)
|
||||
rlst = list_make1(rnode);
|
||||
if (IsTidEqualClause((OpExpr *) expr, varno))
|
||||
rlst = list_make1(expr);
|
||||
}
|
||||
else if (expr && IsA(expr, ScalarArrayOpExpr))
|
||||
{
|
||||
/* another base case: check for tid = ANY clause */
|
||||
if (IsTidEqualAnyClause((ScalarArrayOpExpr *) expr, varno))
|
||||
rlst = list_make1(expr);
|
||||
}
|
||||
else if (and_clause(expr))
|
||||
{
|
||||
foreach(l, ((BoolExpr *) expr)->args)
|
||||
{
|
||||
rlst = TidQualFromExpr(varno, (Node *) lfirst(l));
|
||||
rlst = TidQualFromExpr((Node *) lfirst(l), varno);
|
||||
if (rlst)
|
||||
break;
|
||||
}
|
||||
@ -145,7 +187,8 @@ TidQualFromExpr(int varno, Node *expr)
|
||||
{
|
||||
foreach(l, ((BoolExpr *) expr)->args)
|
||||
{
|
||||
frtn = TidQualFromExpr(varno, (Node *) lfirst(l));
|
||||
List *frtn = TidQualFromExpr((Node *) lfirst(l), varno);
|
||||
|
||||
if (frtn)
|
||||
rlst = list_concat(rlst, frtn);
|
||||
else
|
||||
@ -167,7 +210,7 @@ TidQualFromExpr(int varno, Node *expr)
|
||||
* except for the format of the input.
|
||||
*/
|
||||
static List *
|
||||
TidQualFromRestrictinfo(int varno, List *restrictinfo)
|
||||
TidQualFromRestrictinfo(List *restrictinfo, int varno)
|
||||
{
|
||||
List *rlst = NIL;
|
||||
ListCell *l;
|
||||
@ -178,7 +221,7 @@ TidQualFromRestrictinfo(int varno, List *restrictinfo)
|
||||
|
||||
if (!IsA(rinfo, RestrictInfo))
|
||||
continue; /* probably should never happen */
|
||||
rlst = TidQualFromExpr(varno, (Node *) rinfo->clause);
|
||||
rlst = TidQualFromExpr((Node *) rinfo->clause, varno);
|
||||
if (rlst)
|
||||
break;
|
||||
}
|
||||
@ -194,10 +237,10 @@ TidQualFromRestrictinfo(int varno, List *restrictinfo)
|
||||
void
|
||||
create_tidscan_paths(PlannerInfo *root, RelOptInfo *rel)
|
||||
{
|
||||
List *tideval;
|
||||
List *tidquals;
|
||||
|
||||
tideval = TidQualFromRestrictinfo(rel->relid, rel->baserestrictinfo);
|
||||
tidquals = TidQualFromRestrictinfo(rel->baserestrictinfo, rel->relid);
|
||||
|
||||
if (tideval)
|
||||
add_path(rel, (Path *) create_tidscan_path(root, rel, tideval));
|
||||
if (tidquals)
|
||||
add_path(rel, (Path *) create_tidscan_path(root, rel, tidquals));
|
||||
}
|
||||
|
@ -10,7 +10,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/optimizer/plan/createplan.c,v 1.204 2005/11/25 19:47:49 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/optimizer/plan/createplan.c,v 1.205 2005/11/26 22:14:56 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -92,7 +92,7 @@ static BitmapHeapScan *make_bitmap_heapscan(List *qptlist,
|
||||
List *bitmapqualorig,
|
||||
Index scanrelid);
|
||||
static TidScan *make_tidscan(List *qptlist, List *qpqual, Index scanrelid,
|
||||
List *tideval);
|
||||
List *tidquals);
|
||||
static FunctionScan *make_functionscan(List *qptlist, List *qpqual,
|
||||
Index scanrelid);
|
||||
static BitmapAnd *make_bitmap_and(List *bitmapplans);
|
||||
@ -1149,6 +1149,7 @@ create_tidscan_plan(PlannerInfo *root, TidPath *best_path,
|
||||
{
|
||||
TidScan *scan_plan;
|
||||
Index scan_relid = best_path->path.parent->relid;
|
||||
List *ortidquals;
|
||||
|
||||
/* it should be a base rel... */
|
||||
Assert(scan_relid > 0);
|
||||
@ -1157,13 +1158,22 @@ create_tidscan_plan(PlannerInfo *root, TidPath *best_path,
|
||||
/* Reduce RestrictInfo list to bare expressions */
|
||||
scan_clauses = get_actual_clauses(scan_clauses);
|
||||
|
||||
/*
|
||||
* Remove any clauses that are TID quals. This is a bit tricky since
|
||||
* the tidquals list has implicit OR semantics.
|
||||
*/
|
||||
ortidquals = best_path->tidquals;
|
||||
if (list_length(ortidquals) > 1)
|
||||
ortidquals = list_make1(make_orclause(ortidquals));
|
||||
scan_clauses = list_difference(scan_clauses, ortidquals);
|
||||
|
||||
/* Sort clauses into best execution order */
|
||||
scan_clauses = order_qual_clauses(root, scan_clauses);
|
||||
|
||||
scan_plan = make_tidscan(tlist,
|
||||
scan_clauses,
|
||||
scan_relid,
|
||||
best_path->tideval);
|
||||
best_path->tidquals);
|
||||
|
||||
copy_path_costsize(&scan_plan->scan.plan, &best_path->path);
|
||||
|
||||
@ -1939,7 +1949,7 @@ static TidScan *
|
||||
make_tidscan(List *qptlist,
|
||||
List *qpqual,
|
||||
Index scanrelid,
|
||||
List *tideval)
|
||||
List *tidquals)
|
||||
{
|
||||
TidScan *node = makeNode(TidScan);
|
||||
Plan *plan = &node->scan.plan;
|
||||
@ -1950,7 +1960,7 @@ make_tidscan(List *qptlist,
|
||||
plan->lefttree = NULL;
|
||||
plan->righttree = NULL;
|
||||
node->scan.scanrelid = scanrelid;
|
||||
node->tideval = tideval;
|
||||
node->tidquals = tidquals;
|
||||
|
||||
return node;
|
||||
}
|
||||
|
@ -9,7 +9,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/optimizer/plan/setrefs.c,v 1.118 2005/11/22 18:17:13 momjian Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/optimizer/plan/setrefs.c,v 1.119 2005/11/26 22:14:57 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -170,8 +170,7 @@ set_plan_references(Plan *plan, List *rtable)
|
||||
case T_TidScan:
|
||||
fix_expr_references(plan, (Node *) plan->targetlist);
|
||||
fix_expr_references(plan, (Node *) plan->qual);
|
||||
fix_expr_references(plan,
|
||||
(Node *) ((TidScan *) plan)->tideval);
|
||||
fix_expr_references(plan, (Node *) ((TidScan *) plan)->tidquals);
|
||||
break;
|
||||
case T_SubqueryScan:
|
||||
/* Needs special treatment, see comments below */
|
||||
@ -509,7 +508,7 @@ adjust_plan_varnos(Plan *plan, int rtoffset)
|
||||
((TidScan *) plan)->scan.scanrelid += rtoffset;
|
||||
adjust_expr_varnos((Node *) plan->targetlist, rtoffset);
|
||||
adjust_expr_varnos((Node *) plan->qual, rtoffset);
|
||||
adjust_expr_varnos((Node *) ((TidScan *) plan)->tideval,
|
||||
adjust_expr_varnos((Node *) ((TidScan *) plan)->tidquals,
|
||||
rtoffset);
|
||||
break;
|
||||
case T_SubqueryScan:
|
||||
@ -916,11 +915,11 @@ set_inner_join_references(Plan *inner_plan,
|
||||
TidScan *innerscan = (TidScan *) inner_plan;
|
||||
Index innerrel = innerscan->scan.scanrelid;
|
||||
|
||||
innerscan->tideval = join_references(innerscan->tideval,
|
||||
rtable,
|
||||
outer_itlist,
|
||||
NULL,
|
||||
innerrel);
|
||||
innerscan->tidquals = join_references(innerscan->tidquals,
|
||||
rtable,
|
||||
outer_itlist,
|
||||
NULL,
|
||||
innerrel);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,7 @@
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/optimizer/plan/subselect.c,v 1.101 2005/11/22 18:17:13 momjian Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/optimizer/plan/subselect.c,v 1.102 2005/11/26 22:14:57 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -1024,7 +1024,7 @@ finalize_plan(Plan *plan, List *rtable,
|
||||
break;
|
||||
|
||||
case T_TidScan:
|
||||
finalize_primnode((Node *) ((TidScan *) plan)->tideval,
|
||||
finalize_primnode((Node *) ((TidScan *) plan)->tidquals,
|
||||
&context);
|
||||
break;
|
||||
|
||||
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/optimizer/util/pathnode.c,v 1.125 2005/10/15 02:49:21 momjian Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/optimizer/util/pathnode.c,v 1.126 2005/11/26 22:14:57 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -613,11 +613,10 @@ create_bitmap_or_path(PlannerInfo *root,
|
||||
|
||||
/*
|
||||
* create_tidscan_path
|
||||
* Creates a path corresponding to a tid_direct scan, returning the
|
||||
* pathnode.
|
||||
* Creates a path corresponding to a scan by TID, returning the pathnode.
|
||||
*/
|
||||
TidPath *
|
||||
create_tidscan_path(PlannerInfo *root, RelOptInfo *rel, List *tideval)
|
||||
create_tidscan_path(PlannerInfo *root, RelOptInfo *rel, List *tidquals)
|
||||
{
|
||||
TidPath *pathnode = makeNode(TidPath);
|
||||
|
||||
@ -625,14 +624,9 @@ create_tidscan_path(PlannerInfo *root, RelOptInfo *rel, List *tideval)
|
||||
pathnode->path.parent = rel;
|
||||
pathnode->path.pathkeys = NIL;
|
||||
|
||||
pathnode->tideval = tideval;
|
||||
pathnode->tidquals = tidquals;
|
||||
|
||||
cost_tidscan(&pathnode->path, root, rel, tideval);
|
||||
|
||||
/*
|
||||
* divide selectivity for each clause to get an equal selectivity as
|
||||
* IndexScan does OK ?
|
||||
*/
|
||||
cost_tidscan(&pathnode->path, root, rel, tidquals);
|
||||
|
||||
return pathnode;
|
||||
}
|
||||
|
@ -7,7 +7,7 @@
|
||||
* Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.143 2005/11/26 03:03:07 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.144 2005/11/26 22:14:57 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -972,14 +972,14 @@ typedef struct BitmapHeapScanState
|
||||
* TidScanState information
|
||||
*
|
||||
* NumTids number of tids in this scan
|
||||
* TidPtr current tid in use
|
||||
* TidList evaluated item pointers
|
||||
* TidPtr index of currently fetched tid
|
||||
* TidList evaluated item pointers (array of size NumTids)
|
||||
* ----------------
|
||||
*/
|
||||
typedef struct TidScanState
|
||||
{
|
||||
ScanState ss; /* its first field is NodeTag */
|
||||
List *tss_tideval; /* list of ExprState nodes */
|
||||
List *tss_tidquals; /* list of ExprState nodes */
|
||||
int tss_NumTids;
|
||||
int tss_TidPtr;
|
||||
int tss_MarkTidPtr;
|
||||
|
@ -7,7 +7,7 @@
|
||||
* Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* $PostgreSQL: pgsql/src/include/nodes/plannodes.h,v 1.81 2005/11/22 18:17:31 momjian Exp $
|
||||
* $PostgreSQL: pgsql/src/include/nodes/plannodes.h,v 1.82 2005/11/26 22:14:57 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -255,12 +255,15 @@ typedef struct BitmapHeapScan
|
||||
|
||||
/* ----------------
|
||||
* tid scan node
|
||||
*
|
||||
* tidquals is an implicitly OR'ed list of qual expressions of the form
|
||||
* "CTID = pseudoconstant" or "CTID = ANY(pseudoconstant_array)".
|
||||
* ----------------
|
||||
*/
|
||||
typedef struct TidScan
|
||||
{
|
||||
Scan scan;
|
||||
List *tideval;
|
||||
List *tidquals; /* qual(s) involving CTID = something */
|
||||
} TidScan;
|
||||
|
||||
/* ----------------
|
||||
|
@ -7,7 +7,7 @@
|
||||
* Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* $PostgreSQL: pgsql/src/include/nodes/relation.h,v 1.120 2005/11/14 23:54:23 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/include/nodes/relation.h,v 1.121 2005/11/26 22:14:57 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -503,13 +503,14 @@ typedef struct BitmapOrPath
|
||||
/*
|
||||
* TidPath represents a scan by TID
|
||||
*
|
||||
* tideval is an implicitly OR'ed list of quals of the form CTID = something.
|
||||
* Note they are bare quals, not RestrictInfos.
|
||||
* tidquals is an implicitly OR'ed list of qual expressions of the form
|
||||
* "CTID = pseudoconstant" or "CTID = ANY(pseudoconstant_array)".
|
||||
* Note they are bare expressions, not RestrictInfos.
|
||||
*/
|
||||
typedef struct TidPath
|
||||
{
|
||||
Path path;
|
||||
List *tideval; /* qual(s) involving CTID = something */
|
||||
List *tidquals; /* qual(s) involving CTID = something */
|
||||
} TidPath;
|
||||
|
||||
/*
|
||||
|
@ -7,7 +7,7 @@
|
||||
* Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* $PostgreSQL: pgsql/src/include/optimizer/cost.h,v 1.71 2005/10/15 02:49:45 momjian Exp $
|
||||
* $PostgreSQL: pgsql/src/include/optimizer/cost.h,v 1.72 2005/11/26 22:14:57 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -61,7 +61,7 @@ extern void cost_bitmap_and_node(BitmapAndPath *path, PlannerInfo *root);
|
||||
extern void cost_bitmap_or_node(BitmapOrPath *path, PlannerInfo *root);
|
||||
extern void cost_bitmap_tree_node(Path *path, Cost *cost, Selectivity *selec);
|
||||
extern void cost_tidscan(Path *path, PlannerInfo *root,
|
||||
RelOptInfo *baserel, List *tideval);
|
||||
RelOptInfo *baserel, List *tidquals);
|
||||
extern void cost_subqueryscan(Path *path, RelOptInfo *baserel);
|
||||
extern void cost_functionscan(Path *path, PlannerInfo *root,
|
||||
RelOptInfo *baserel);
|
||||
|
@ -7,7 +7,7 @@
|
||||
* Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* $PostgreSQL: pgsql/src/include/optimizer/pathnode.h,v 1.62 2005/10/15 02:49:45 momjian Exp $
|
||||
* $PostgreSQL: pgsql/src/include/optimizer/pathnode.h,v 1.63 2005/11/26 22:14:57 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -45,7 +45,7 @@ extern BitmapOrPath *create_bitmap_or_path(PlannerInfo *root,
|
||||
RelOptInfo *rel,
|
||||
List *bitmapquals);
|
||||
extern TidPath *create_tidscan_path(PlannerInfo *root, RelOptInfo *rel,
|
||||
List *tideval);
|
||||
List *tidquals);
|
||||
extern AppendPath *create_append_path(RelOptInfo *rel, List *subpaths);
|
||||
extern ResultPath *create_result_path(RelOptInfo *rel, Path *subpath,
|
||||
List *constantqual);
|
||||
|
Loading…
Reference in New Issue
Block a user