Instead of rechecking lossy index operators by putting them into the

regular qpqual ('filter condition'), add special-purpose code to
nodeIndexscan.c to recheck them.  This ends being almost no net addition
of code, because the removal of planner code balances out the extra
executor code, but it is significantly more efficient when a lossy
operator is involved in an OR indexscan.  The old implementation had
to recheck the entire indexqual in such cases.
This commit is contained in:
Tom Lane 2004-01-06 04:31:01 +00:00
parent fa559a86ee
commit b0c4a50bbb
8 changed files with 134 additions and 114 deletions

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/executor/nodeIndexscan.c,v 1.88 2003/12/30 20:05:05 tgl Exp $
* $PostgreSQL: pgsql/src/backend/executor/nodeIndexscan.c,v 1.89 2004/01/06 04:31:01 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -99,7 +99,9 @@ IndexNext(IndexScanState *node)
ExprContext *econtext;
ScanDirection direction;
IndexScanDescPtr scanDescs;
List **lossyQuals;
IndexScanDesc scandesc;
List *lossyQual;
Index scanrelid;
HeapTuple tuple;
TupleTableSlot *slot;
@ -120,6 +122,7 @@ IndexNext(IndexScanState *node)
direction = ForwardScanDirection;
}
scanDescs = node->iss_ScanDescs;
lossyQuals = node->iss_LossyQuals;
numIndices = node->iss_NumIndices;
econtext = node->ss.ps.ps_ExprContext;
slot = node->ss.ss_ScanTupleSlot;
@ -188,6 +191,8 @@ IndexNext(IndexScanState *node)
while (indexNumber < numIndices)
{
scandesc = scanDescs[node->iss_IndexPtr];
lossyQual = lossyQuals[node->iss_IndexPtr];
while ((tuple = index_getnext(scandesc, direction)) != NULL)
{
/*
@ -201,6 +206,22 @@ IndexNext(IndexScanState *node)
scandesc->xs_cbuf, /* buffer containing tuple */
false); /* don't pfree */
/*
* If any of the index operators involved in this scan are lossy,
* recheck them by evaluating the original operator clauses.
*/
if (lossyQual)
{
econtext->ecxt_scantuple = slot;
ResetExprContext(econtext);
if (!ExecQual(lossyQual, econtext, false))
{
/* Fails lossy op, so drop it and loop back for another */
ExecClearTuple(slot);
continue;
}
}
/*
* If it's a multiple-index scan, make sure not to double-report
* a tuple matched by more than one index. (See notes above.)
@ -615,6 +636,7 @@ ExecInitIndexScan(IndexScan *node, EState *estate)
List *indxqual;
List *indxstrategy;
List *indxsubtype;
List *indxlossy;
List *indxid;
int i;
int numIndices;
@ -623,6 +645,7 @@ ExecInitIndexScan(IndexScan *node, EState *estate)
int *numScanKeys;
RelationPtr indexDescs;
IndexScanDescPtr scanDescs;
List **lossyQuals;
ExprState ***runtimeKeyInfo;
bool have_runtime_keys;
RangeTblEntry *rtentry;
@ -680,6 +703,7 @@ ExecInitIndexScan(IndexScan *node, EState *estate)
indexstate->iss_RuntimeKeysReady = false;
indexstate->iss_RelationDescs = NULL;
indexstate->iss_ScanDescs = NULL;
indexstate->iss_LossyQuals = NULL;
/*
* get the index node information
@ -699,6 +723,7 @@ ExecInitIndexScan(IndexScan *node, EState *estate)
scanKeys = (ScanKey *) palloc(numIndices * sizeof(ScanKey));
indexDescs = (RelationPtr) palloc(numIndices * sizeof(Relation));
scanDescs = (IndexScanDescPtr) palloc(numIndices * sizeof(IndexScanDesc));
lossyQuals = (List **) palloc0(numIndices * sizeof(List *));
/*
* initialize space for runtime key info (may not be needed)
@ -712,11 +737,13 @@ ExecInitIndexScan(IndexScan *node, EState *estate)
indxqual = node->indxqual;
indxstrategy = node->indxstrategy;
indxsubtype = node->indxsubtype;
indxlossy = node->indxlossy;
for (i = 0; i < numIndices; i++)
{
List *quals;
List *strategies;
List *subtypes;
List *lossyflags;
int n_keys;
ScanKey scan_keys;
ExprState **run_keys;
@ -728,6 +755,8 @@ ExecInitIndexScan(IndexScan *node, EState *estate)
indxstrategy = lnext(indxstrategy);
subtypes = lfirst(indxsubtype);
indxsubtype = lnext(indxsubtype);
lossyflags = lfirst(indxlossy);
indxlossy = lnext(indxlossy);
n_keys = length(quals);
scan_keys = (n_keys <= 0) ? (ScanKey) NULL :
(ScanKey) palloc(n_keys * sizeof(ScanKeyData));
@ -747,6 +776,7 @@ ExecInitIndexScan(IndexScan *node, EState *estate)
AttrNumber varattno; /* att number used in scan */
StrategyNumber strategy; /* op's strategy number */
Oid subtype; /* op's strategy subtype */
int lossy; /* op's recheck flag */
RegProcedure opfuncid; /* operator proc id used in scan */
Datum scanvalue; /* value used in scan (if const) */
@ -759,6 +789,8 @@ ExecInitIndexScan(IndexScan *node, EState *estate)
strategies = lnext(strategies);
subtype = lfirsto(subtypes);
subtypes = lnext(subtypes);
lossy = lfirsti(lossyflags);
lossyflags = lnext(lossyflags);
if (!IsA(clause, OpExpr))
elog(ERROR, "indxqual is not an OpExpr");
@ -839,6 +871,20 @@ ExecInitIndexScan(IndexScan *node, EState *estate)
subtype, /* strategy subtype */
opfuncid, /* reg proc to use */
scanvalue); /* constant */
/*
* If this operator is lossy, add its indxqualorig expression
* to the list of quals to recheck. The nth() calls here could
* be avoided by chasing the lists in parallel to all the other
* lists, but since lossy operators are very uncommon, it's
* probably a waste of time to do so.
*/
if (lossy)
{
lossyQuals[i] = lappend(lossyQuals[i],
nth(j,
(List *) nth(i, indexstate->indxqualorig)));
}
}
/*
@ -928,6 +974,7 @@ ExecInitIndexScan(IndexScan *node, EState *estate)
indexstate->iss_RelationDescs = indexDescs;
indexstate->iss_ScanDescs = scanDescs;
indexstate->iss_LossyQuals = lossyQuals;
/*
* Initialize result tuple type and projection info.

View File

@ -15,7 +15,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.273 2004/01/05 05:07:35 tgl Exp $
* $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.274 2004/01/06 04:31:01 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -284,6 +284,17 @@ _copyIndexScan(IndexScan *from)
}
newnode->indxsubtype = newsubtype;
}
/* this can become COPY_NODE_FIELD when intlists are normal objects: */
{
List *newstrat = NIL;
List *tmp;
foreach(tmp, from->indxlossy)
{
newstrat = lappend(newstrat, listCopy(lfirst(tmp)));
}
newnode->indxlossy = newstrat;
}
COPY_SCALAR_FIELD(indxorderdir);
return newnode;

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.228 2004/01/05 23:39:53 tgl Exp $
* $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.229 2004/01/06 04:31:01 tgl Exp $
*
* NOTES
* Every node type that can appear in stored rules' parsetrees *must*
@ -356,6 +356,16 @@ _outIndexScan(StringInfo str, IndexScan *node)
_outOidList(str, lfirst(tmp));
}
}
/* this can become WRITE_NODE_FIELD when intlists are normal objects: */
{
List *tmp;
appendStringInfo(str, " :indxlossy ");
foreach(tmp, node->indxlossy)
{
_outIntList(str, lfirst(tmp));
}
}
WRITE_ENUM_FIELD(indxorderdir, ScanDirection);
}

View File

@ -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.121 2004/01/05 23:39:54 tgl Exp $
* $PostgreSQL: pgsql/src/backend/optimizer/path/costsize.c,v 1.122 2004/01/06 04:31:01 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -384,10 +384,6 @@ cost_index(Path *path, Query *root,
* some of the indexquals are join clauses and shouldn't be
* subtracted. Rather than work out exactly how much to subtract, we
* don't subtract anything.
*
* XXX For a lossy index, not all the quals will be removed and so we
* really shouldn't subtract their costs; but detecting that seems
* more expensive than it's worth.
*/
startup_cost += baserel->baserestrictcost.startup;
cpu_per_tuple = cpu_tuple_cost + baserel->baserestrictcost.per_tuple;
@ -397,6 +393,7 @@ cost_index(Path *path, Query *root,
QualCost index_qual_cost;
cost_qual_eval(&index_qual_cost, indexQuals);
/* any startup cost still has to be paid ... */
cpu_per_tuple -= index_qual_cost.per_tuple;
}

View File

@ -10,7 +10,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/optimizer/plan/createplan.c,v 1.164 2004/01/05 23:39:54 tgl Exp $
* $PostgreSQL: pgsql/src/backend/optimizer/plan/createplan.c,v 1.165 2004/01/06 04:31:01 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -62,16 +62,16 @@ static HashJoin *create_hashjoin_plan(Query *root, HashPath *best_path,
Plan *outer_plan, Plan *inner_plan);
static void fix_indxqual_references(List *indexquals, IndexPath *index_path,
List **fixed_indexquals,
List **recheck_indexquals,
List **indxstrategy,
List **indxsubtype);
List **indxsubtype,
List **indxlossy);
static void fix_indxqual_sublist(List *indexqual,
Relids baserelids, int baserelid,
IndexOptInfo *index,
List **fixed_quals,
List **recheck_quals,
List **strategy,
List **subtype);
List **subtype,
List **lossy);
static Node *fix_indxqual_operand(Node *node, int baserelid,
IndexOptInfo *index,
Oid *opclass);
@ -82,7 +82,7 @@ static void copy_plan_costsize(Plan *dest, Plan *src);
static SeqScan *make_seqscan(List *qptlist, List *qpqual, Index scanrelid);
static IndexScan *make_indexscan(List *qptlist, List *qpqual, Index scanrelid,
List *indxid, List *indxqual, List *indxqualorig,
List *indxstrategy, List *indxsubtype,
List *indxstrategy, List *indxsubtype, List *indxlossy,
ScanDirection indexscandir);
static TidScan *make_tidscan(List *qptlist, List *qpqual, Index scanrelid,
List *tideval);
@ -718,9 +718,9 @@ create_indexscan_plan(Query *root,
Expr *indxqual_or_expr = NULL;
List *stripped_indxquals;
List *fixed_indxquals;
List *recheck_indxquals;
List *indxstrategy;
List *indxsubtype;
List *indxlossy;
FastList indexids;
List *i;
IndexScan *scan_plan;
@ -735,8 +735,8 @@ create_indexscan_plan(Query *root,
* value is just the rel's baserestrictinfo list). We must add these
* clauses to scan_clauses to ensure they get checked. In most cases
* we will remove the join clauses again below, but if a join clause
* contains a lossy or special operator, we need to make sure it gets
* into scan_clauses.
* contains a special operator, we need to make sure it gets into the
* scan_clauses.
*/
if (best_path->isjoininner)
{
@ -780,22 +780,19 @@ create_indexscan_plan(Query *root,
/*
* The qpqual list must contain all restrictions not automatically
* handled by the index. Normally the predicates in the indexquals are
* checked fully by the index, but if the index is "lossy" for a
* particular operator (as signaled by the amopreqcheck flag in
* pg_amop), then we need to double-check that predicate in qpqual,
* because the index may return more tuples than match the predicate.
*
* Since the indexquals were generated from the restriction clauses given
* by scan_clauses, there will normally be duplications between the lists.
* We get rid of the duplicates, then add back if lossy.
* handled by the index. All the predicates in the indexquals will
* be checked (either by the index itself, or by nodeIndexscan.c), but
* if there are any "special" operators involved then they must be
* added to qpqual. The upshot is that qpquals must contain scan_clauses
* minus whatever appears in indxquals.
*/
if (length(indxquals) > 1)
{
/*
* Build an expression representation of the indexqual, expanding
* the implicit OR and AND semantics of the first- and
* second-level lists.
* second-level lists. (The odds that this will exactly match any
* scan_clause are not great; perhaps we need more smarts here.)
*/
indxqual_or_expr = make_expr_from_indexclauses(indxquals);
qpqual = set_difference(scan_clauses, makeList1(indxqual_or_expr));
@ -814,34 +811,11 @@ create_indexscan_plan(Query *root,
/*
* The executor needs a copy with the indexkey on the left of each
* clause and with index attr numbers substituted for table ones. This
* pass also looks for "lossy" operators.
* pass also gets strategy info and looks for "lossy" operators.
*/
fix_indxqual_references(indxquals, best_path,
&fixed_indxquals, &recheck_indxquals,
&indxstrategy, &indxsubtype);
/*
* If there were any "lossy" operators, need to add back the
* appropriate qual clauses to the qpqual. When there is just one
* indexscan being performed (ie, we have simple AND semantics), we
* can just add the lossy clauses themselves to qpqual. If we have
* OR-of-ANDs, we'd better add the entire original indexquals to make
* sure that the semantics are correct.
*/
if (recheck_indxquals != NIL)
{
if (indxqual_or_expr)
{
/* Better do a deep copy of the original scanclauses */
qpqual = lappend(qpqual, copyObject(indxqual_or_expr));
}
else
{
/* Subroutine already copied quals, so just append to list */
Assert(length(recheck_indxquals) == 1);
qpqual = nconc(qpqual, (List *) lfirst(recheck_indxquals));
}
}
&fixed_indxquals,
&indxstrategy, &indxsubtype, &indxlossy);
/* Finally ready to build the plan node */
scan_plan = make_indexscan(tlist,
@ -852,6 +826,7 @@ create_indexscan_plan(Query *root,
stripped_indxquals,
indxstrategy,
indxsubtype,
indxlossy,
best_path->indexscandir);
copy_path_costsize(&scan_plan->scan.plan, &best_path->path);
@ -1197,76 +1172,64 @@ create_hashjoin_plan(Query *root,
* Adjust indexqual clauses to the form the executor's indexqual
* machinery needs, and check for recheckable (lossy) index conditions.
*
* We have five tasks here:
* We have four tasks here:
* * Remove RestrictInfo nodes from the input clauses.
* * Index keys must be represented by Var nodes with varattno set to the
* index's attribute number, not the attribute number in the original rel.
* * If the index key is on the right, commute the clause to put it on the
* left. (Someday the executor might not need this, but for now it does.)
* * If the indexable operator is marked 'amopreqcheck' in pg_amop, then
* the index is "lossy" for this operator: it may return more tuples than
* actually satisfy the operator condition. For each such operator, we
* must add (the original form of) the indexqual clause to the "qpquals"
* of the indexscan node, where the operator will be re-evaluated to
* ensure it passes.
* * We must construct lists of operator strategy numbers and subtypes for
* the top-level operators of each index clause.
* * We must construct lists of operator strategy numbers, subtypes, and
* recheck (lossy-operator) flags for the top-level operators of each
* index clause.
*
* Both the input list and the output lists have the form of lists of sublists
* of qual clauses --- the top-level list has one entry for each indexscan
* to be performed. The semantics are OR-of-ANDs. Note however that the
* input list contains RestrictInfos, while the output lists do not.
* Both the input list and the "fixed" output list have the form of lists of
* sublists of qual clauses --- the top-level list has one entry for each
* indexscan to be performed. The semantics are OR-of-ANDs. Note however
* that the input list contains RestrictInfos, while the output list doesn't.
*
* fixed_indexquals receives a modified copy of the indexqual list --- the
* original is not changed. Note also that the copy shares no substructure
* with the original; this is needed in case there is a subplan in it (we need
* two separate copies of the subplan tree, or things will go awry).
*
* recheck_indexquals similarly receives a copy of whichever clauses
* need rechecking.
*
* indxstrategy receives a list of integer sublists of strategy numbers.
* indxsubtype receives a list of OID sublists of strategy subtypes.
* indxlossy receives a list of integer sublists of lossy-operator booleans.
*/
static void
fix_indxqual_references(List *indexquals, IndexPath *index_path,
List **fixed_indexquals, List **recheck_indexquals,
List **indxstrategy, List **indxsubtype)
List **fixed_indexquals,
List **indxstrategy,
List **indxsubtype,
List **indxlossy)
{
FastList fixed_quals;
FastList recheck_quals;
Relids baserelids = index_path->path.parent->relids;
int baserelid = index_path->path.parent->relid;
List *ixinfo = index_path->indexinfo;
List *i;
FastListInit(&fixed_quals);
FastListInit(&recheck_quals);
*fixed_indexquals = NIL;
*indxstrategy = NIL;
*indxsubtype = NIL;
*indxlossy = NIL;
foreach(i, indexquals)
{
List *indexqual = lfirst(i);
IndexOptInfo *index = (IndexOptInfo *) lfirst(ixinfo);
List *fixed_qual;
List *recheck_qual;
List *strategy;
List *subtype;
List *lossy;
fix_indxqual_sublist(indexqual, baserelids, baserelid, index,
&fixed_qual, &recheck_qual,
&strategy, &subtype);
FastAppend(&fixed_quals, fixed_qual);
if (recheck_qual != NIL)
FastAppend(&recheck_quals, recheck_qual);
&fixed_qual, &strategy, &subtype, &lossy);
*fixed_indexquals = lappend(*fixed_indexquals, fixed_qual);
*indxstrategy = lappend(*indxstrategy, strategy);
*indxsubtype = lappend(*indxsubtype, subtype);
*indxlossy = lappend(*indxlossy, lossy);
ixinfo = lnext(ixinfo);
}
*fixed_indexquals = FastListValue(&fixed_quals);
*recheck_indexquals = FastListValue(&recheck_quals);
}
/*
@ -1274,34 +1237,30 @@ fix_indxqual_references(List *indexquals, IndexPath *index_path,
*
* For each qual clause, commute if needed to put the indexkey operand on the
* left, and then fix its varattno. (We do not need to change the other side
* of the clause.) Also change the operator if necessary, check for
* lossy index behavior, and determine the operator's strategy number and
* subtype number.
* of the clause.) Then determine the operator's strategy number and subtype
* number, and check for lossy index behavior.
*
* Returns four lists:
* the list of fixed indexquals
* the list (usually empty) of original clauses that must be rechecked
* as qpquals because the index is lossy for this operator type
* the integer list of strategy numbers
* the OID list of strategy subtypes
* the integer list of lossiness flags (1/0)
*/
static void
fix_indxqual_sublist(List *indexqual,
Relids baserelids, int baserelid,
IndexOptInfo *index,
List **fixed_quals,
List **recheck_quals,
List **strategy,
List **subtype)
List **subtype,
List **lossy)
{
FastList fixed_qual;
FastList recheck_qual;
List *i;
FastListInit(&fixed_qual);
FastListInit(&recheck_qual);
*fixed_quals = NIL;
*strategy = NIL;
*subtype = NIL;
*lossy = NIL;
foreach(i, indexqual)
{
RestrictInfo *rinfo = (RestrictInfo *) lfirst(i);
@ -1344,29 +1303,20 @@ fix_indxqual_sublist(List *indexqual,
index,
&opclass);
FastAppend(&fixed_qual, newclause);
*fixed_quals = lappend(*fixed_quals, newclause);
/*
* Look up the operator in the operator class to get its strategy
* numbers and the recheck indicator. This also double-checks that
* we found an operator matching the index.
* Look up the (possibly commuted) operator in the operator class to
* get its strategy numbers and the recheck indicator. This also
* double-checks that we found an operator matching the index.
*/
get_op_opclass_properties(newclause->opno, opclass,
&stratno, &stratsubtype, &recheck);
*strategy = lappendi(*strategy, stratno);
*subtype = lappendo(*subtype, stratsubtype);
/*
* If index is lossy for this operator, add (a copy of) original form
* of clause to recheck list.
*/
if (recheck)
FastAppend(&recheck_qual, copyObject((Node *) clause));
*lossy = lappendi(*lossy, (int) recheck);
}
*fixed_quals = FastListValue(&fixed_qual);
*recheck_quals = FastListValue(&recheck_qual);
}
static Node *
@ -1612,6 +1562,7 @@ make_indexscan(List *qptlist,
List *indxqualorig,
List *indxstrategy,
List *indxsubtype,
List *indxlossy,
ScanDirection indexscandir)
{
IndexScan *node = makeNode(IndexScan);
@ -1628,6 +1579,7 @@ make_indexscan(List *qptlist,
node->indxqualorig = indxqualorig;
node->indxstrategy = indxstrategy;
node->indxsubtype = indxsubtype;
node->indxlossy = indxlossy;
node->indxorderdir = indexscandir;
return node;

View File

@ -9,7 +9,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/optimizer/plan/setrefs.c,v 1.99 2003/11/29 19:51:50 pgsql Exp $
* $PostgreSQL: pgsql/src/backend/optimizer/plan/setrefs.c,v 1.100 2004/01/06 04:31:01 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -362,8 +362,8 @@ set_join_references(Join *join, List *rtable)
/*
* We must fix the inner qpqual too, if it has join
* clauses (this could happen if the index is lossy: some
* indxquals may get rechecked as qpquals).
* clauses (this could happen if special operators are
* involved: some indxquals may get rechecked as qpquals).
*/
if (NumRelids((Node *) inner_plan->qual) > 1)
inner_plan->qual = join_references(inner_plan->qual,

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.109 2003/12/18 22:23:42 tgl Exp $
* $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.110 2004/01/06 04:31:01 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -776,6 +776,7 @@ typedef ScanState SeqScanState;
* RuntimeKeysReady true if runtime Skeys have been computed
* RelationDescs ptr to array of relation descriptors
* ScanDescs ptr to array of scan descriptors
* LossyQuals ptr to array of qual lists for lossy operators
* DupHash hashtable for recognizing dups in multiple scan
* MaxHash max # entries we will allow in hashtable
* ----------------
@ -795,6 +796,7 @@ typedef struct IndexScanState
bool iss_RuntimeKeysReady;
RelationPtr iss_RelationDescs;
IndexScanDescPtr iss_ScanDescs;
List **iss_LossyQuals;
HTAB *iss_DupHash;
long iss_MaxHash;
} IndexScanState;

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/nodes/plannodes.h,v 1.72 2003/11/29 22:41:06 pgsql Exp $
* $PostgreSQL: pgsql/src/include/nodes/plannodes.h,v 1.73 2004/01/06 04:31:01 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -166,6 +166,7 @@ typedef struct IndexScan
List *indxqualorig; /* the same in original form */
List *indxstrategy; /* list of sublists of strategy numbers */
List *indxsubtype; /* list of sublists of strategy subtypes */
List *indxlossy; /* list of sublists of lossy flags (ints) */
ScanDirection indxorderdir; /* forward or backward or don't care */
} IndexScan;