oracle.kv.impl.query.compiler.IndexAnalyzer Maven / Gradle / Ivy
/*-
* Copyright (C) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
*
* This file was distributed by Oracle as part of a version of Oracle NoSQL
* Database made available at:
*
* http://www.oracle.com/technetwork/database/database-technologies/nosqldb/downloads/index.html
*
* Please see the LICENSE file included in the top-level directory of the
* appropriate version of Oracle NoSQL Database for a copy of the license and
* additional information.
*/
package oracle.kv.impl.query.compiler;
import java.util.ArrayList;
import java.util.List;
import java.util.HashMap;
import oracle.kv.impl.api.table.BooleanValueImpl;
import oracle.kv.impl.api.table.EmptyValueImpl;
import oracle.kv.impl.api.table.EnumDefImpl;
import oracle.kv.impl.api.table.FieldDefImpl;
import oracle.kv.impl.api.table.FieldValueImpl;
import oracle.kv.impl.api.table.Geometry;
import oracle.kv.impl.api.table.IndexImpl;
import oracle.kv.impl.api.table.IndexImpl.IndexField;
import oracle.kv.impl.api.table.IndexKeyImpl;
import oracle.kv.impl.api.table.NullValueImpl;
import oracle.kv.impl.api.table.PrimaryKeyImpl;
import oracle.kv.impl.api.table.RecordDefImpl;
import oracle.kv.impl.api.table.TableImpl;
import oracle.kv.impl.api.table.TimestampDefImpl;
import oracle.kv.impl.query.QueryException;
import oracle.kv.impl.query.QueryException.Location;
import oracle.kv.impl.query.QueryStateException;
import oracle.kv.impl.query.compiler.Expr.ConstKind;
import oracle.kv.impl.query.compiler.Expr.ExprIter;
import oracle.kv.impl.query.compiler.Expr.ExprKind;
import oracle.kv.impl.query.compiler.ExprSFW.FromClause;
import oracle.kv.impl.query.compiler.FunctionLib.FuncCode;
import oracle.kv.impl.query.types.TypeManager;
import oracle.kv.impl.util.Pair;
import oracle.kv.table.FieldDef.Type;
import oracle.kv.table.FieldRange;
/**
* An IndexAnalyzer is associated with a SFW expr and an index I on a table T
* that appears in the FROM clause of the SFW. I may be the primary index of
* T. The IndexAnalyzer tries to find the best way to replace a full table scan
* on T with an index scan on I. It does so by examinig the exprs appearing in
* SFW clauses to determine which ones can be evaluated by the info stored in
* the index.
*
* Some terminology:
* -----------------
*
* MapBoth index:
* An index that indexes both the keys and the elements of a map, with the keys
* indexed before all the element paths. for example, an index on
* (keys(map), map[].mf1, col2, map[].mf2, map[].mf3) is a MapBoth index.
*
* Start/stop predicate:
* A pred that can participate in determining a starting and/or ending point
* for an index range scan. Currently, potential start/stop preds are the ones
* that have one of the following forms:
* - expr op const
* - expr IS (NOT) NULL
* - EXISTS expr
* where expr is a path expr that "matches" one of the index paths, and
* op is a comparison operator (value or "anu" comparison). Notice that
* whether a pred having one of the above forms will actually be used as a
* start/stop pred depends on what other preds exists in the query.
*
* Index-filtering predicate:
* A pred that can be applied on each index entry during an index scan,
* because its evaluation depends only on the field values of the current
* index entry.
*
* Sargable predicate (a.k.a index predicate):
* A pred that is either a start/stop or an index-filtering pred.
*
* Top-level predicate:
* If the root of WHERE-clause expression is an AND operator, the operands of
* this AND are the top-level preds of theSFW. Otherwise, the whole WHERE-clause
* expr is the single top-level pred of theSFW.
*
* Predicate factor:
* A top-level pred may be a composite pred, consisting of a number of pred
* factors. There are 2 cases of composite preds:
*
* 1. A pred that involves a path expr containing filtering steps.
* For example, assume an index on (a.b.e, a.b.c[].d1, a.b.c[].d2).
*
* The pred: a.b[$elem.e = 5].c[$elem.d1 < 10].d2 =any 20 consists of 3 pred
* factors: a.b.e = 5, a.b.c[$elem.d1 < 10], and a.b.c[$elem.d2 = 20].
* All of these pred factors are sargable, and they can all be applied
* together during the index scan.
*
* The pred a.b[$elem.e = 5 and $elem.c[].d1 {@literal {
static int theTrace = 0;
/*
* The relative value of each kind of predicate. Used to compute a
* score for each each index in order to choose the "best" applicable
* index (see getScore() and compareTo() methods).
*/
final static int eqValue = 32;
final static int vrangeValue = 16; // value-range pred
final static int arangeValue = 8; // any-range pred
final static int filterEqValue = 17;
final static int filterOtherValue = 7;
private final QueryControlBlock theQCB;
private final StaticContext theSctx;
ExprSFW theSFW;
private final ExprBaseTable theTableExpr;
private final TableImpl theTable;
private final int theTablePos;
private final int theTargetTablePos;
private final IndexImpl theIndex;
private final boolean theIsHintIndex;
private final boolean theIsPrimary;
private final boolean theIsMapBothIndex;
private final int theNumFields;
final List theIndexPaths;
private final ArrayList theWherePreds;
private final ArrayList thePredGroups;
private PredGroup theUnnestedGroup;
private final ArrayList> theStartStopPreds;
private PredGroup theBestPredGroup;
private boolean theHaveMapKeyEqPred;
private final ArrayList theFilteringPreds;
private final HashMap> theExprRewriteMap;
private ArrayList thePrimaryKeys;
private ArrayList theSecondaryKeys;
private ArrayList theRanges;
private final ArrayList thePushedExternals;
private boolean theHavePushedExternals;
private boolean theIsMultiKeyRange;
private boolean theIsCovering;
private boolean theEliminateDups;
private boolean theEliminateDupsAtServer;
private int theScore = -1;
private int theScore2 = -1;
private int theNumEqPredsPushed = 0;
static private class ExprToReplace {
Expr theExpr;
int theIndexFieldPos;
ExprToReplace(Expr expr, int pos) {
theExpr = expr;
theIndexFieldPos = pos;
}
}
/**
* Information for a top-level WHERE-clause predicate.
*
* thePred:
* The full expr for this top-level pred.
*
* thePredInfos:
* The pred factors of this top-level pred.
*
* theDoesSlicing:
* Set to true if there is any path expr in this top-level pred that
* contains a slicing array step. Such a step cannot be evaluated by
* the index, and as a result, the top-level pred cannot be removed
* from the WHERE clause.
*
* theLocalGroup:
* A group of pred factors that belong to this top-level pred and whose
* outer-most context var is positioned in the multikey step of the index.
* The group also includes the "direct" pred factor (if any) of the top-
* level pred, i.e. the pred factor that has no context var. All such
* preds can be applied together. Any other pred factor (whose outer-most
* context var is positioned before the multikey step of the index) is
* placed in one PredGroup by themselves. This is too strict in that some
* "non-local" pred factors may actually belong together in a pred group
* (for example see query json_idx/q/filter13). However doing something
* better would take a lot of effort, which is probably not worth.
*/
private class WherePredInfo {
int theId;
Expr thePred;
final ArrayList thePredInfos = new ArrayList(8);
boolean theDoesSlicing;
PredGroup theLocalGroup;
WherePredInfo(Expr pred) {
theId = theWherePreds.size();
thePred = pred;
}
boolean add(PredInfo pi) {
if (pi.thePred == null) {
if (theTrace >= 2) {
System.out.println(
"Collected keys() pred for MapBoth key " +
pi.mapBothKey());
}
thePredInfos.add(0, pi);
return true;
}
boolean added = false;
if (pi.isExists()) {
Expr input = pi.thePred.getInput();
if (input.getKind() == ExprKind.ARRAY_FILTER) {
ExprArrayFilter step = (ExprArrayFilter)input;
if (step.getPredExpr() == null) {
thePredInfos.add(pi);
added = true;
}
} else if (input.getKind() == ExprKind.MAP_FILTER) {
ExprMapFilter step = (ExprMapFilter)input;
if (step.getPredExpr() == null) {
thePredInfos.add(pi);
added = true;
}
} else {
thePredInfos.add(pi);
added = true;
}
} else {
thePredInfos.add(pi);
added = true;
}
if (added && theTrace >= 2) {
System.out.println(
"WPI: " + theId + " Collected pred with status " +
pi.theStatus + "\nepath = " +
(pi.theEpath != null ? pi.theEpath.getPathName() : null) +
"\n" + pi.thePred.display() + "\n");
}
return added;
}
boolean doesFiltering() {
return (thePredInfos.size() > 1 ||
thePredInfos.get(0).thePred != thePred);
}
boolean isFullyPushable() {
if (theDoesSlicing) {
return false;
}
for (PredInfo pi : thePredInfos) {
if ((pi.theStatus != PredicateStatus.STARTSTOP &&
pi.theStatus != PredicateStatus.FILTERING &&
pi.theStatus != PredicateStatus.TRUE) ||
(pi.theEpath != null && pi.isUnnested()) ||
pi.isGeo()) {
return false;
}
}
return true;
}
}
/*
* Information about a pred factor.
*/
private class PredInfo {
WherePredInfo theEnclosingPred;
Expr thePred;
FuncCode theOp;
boolean theIsValueComp;
boolean theIsExists;
boolean theIsGeo;
Expr theVarArg;
Expr theConstArg;
FieldValueImpl theConstVal;
Expr theDistanceArg;
double theDistance = -1;
Geometry theGeom;
IndexExpr theEpath;
int theIPathPos = -1;
PredGroup thePredGroup;
PredicateStatus theStatus;
PredInfo(WherePredInfo enclosingPred, Expr pred) {
theEnclosingPred = enclosingPred;
thePred = pred;
theStatus = PredicateStatus.UNKNOWN;
}
boolean isEq() {
return theOp == FuncCode.OP_EQ;
}
boolean isMin() {
return (theOp == FuncCode.OP_GT || theOp == FuncCode.OP_GE);
}
boolean isMax() {
return (theOp == FuncCode.OP_LT || theOp == FuncCode.OP_LE);
}
boolean isInclusive() {
return (theOp == FuncCode.OP_GE || theOp == FuncCode.OP_LE);
}
boolean isExists() {
return theIsExists;
}
boolean isGeo() {
return theIsGeo;
}
boolean isNear() {
return theOp == FuncCode.FN_GEO_WITHIN_DISTANCE;
}
boolean isUnnested() {
return theEpath.theIsUnnested;
}
String mapBothKey() {
return theEpath.getMapBothKey();
}
boolean isMatched() {
return theIPathPos >= 0;
}
IndexField getIndexPath() {
return theIndexPaths.get(theIPathPos);
}
boolean isCompatible(PredInfo other) {
assert(theIPathPos == other.theIPathPos);
return (!theIndexPaths.get(theIPathPos).isMultiKey() ||
thePredGroup == other.thePredGroup);
}
boolean canBeRemoved() {
return (isMatched() &&
thePred != theEnclosingPred.thePred &&
theEpath != null &&
(!getIndexPath().isMultiKey() ||
theEpath.getMapBothKey(theTable, theIndex) != null) &&
theEpath.getFilteringPreds() == null);
}
Location getLocation() {
return thePred.getLocation();
}
@Override
public String toString() {
if (thePred != null) {
return thePred.display();
}
return ("Map Both key = " + mapBothKey());
}
}
/*
* Information about a group of compatible pred factors.
*
* theScore data field and the data fields after it are "transient" ones:
* they are used only for scoring the group for the purpose of choosing the
* "best" one.
*/
private static class PredGroup {
int theId;
final ArrayList thePredInfos = new ArrayList(8);
ExprVar theCtxVar;
String theMapBothKey;
boolean theIsUnnested;
int theScore;
int theFieldScore;
boolean theFoundRange;
boolean theFilteringOnly;
PredGroup(int id, PredInfo pi) {
theId = id;
thePredInfos.add(pi);
pi.thePredGroup = this;
}
static void addUnnestedPred(IndexAnalyzer idx, PredInfo pi) {
if (idx.theUnnestedGroup == null) {
PredGroup pg = idx.addPredGroup(pi);
pg.theIsUnnested = true;
idx.theUnnestedGroup = pg;
return;
}
idx.theUnnestedGroup.thePredInfos.add(pi);
pi.thePredGroup = idx.theUnnestedGroup;
}
static boolean addMapBothPred(IndexAnalyzer idx, PredInfo pi) {
boolean added = false;
for (PredGroup pg : idx.thePredGroups) {
if (pi.mapBothKey().equals(pg.theMapBothKey)) {
pg.thePredInfos.add(pi);
pi.thePredGroup = pg;
added = true;
break;
}
}
if (!added) {
PredGroup pg = idx.addPredGroup(pi);
pg.theMapBothKey = pi.mapBothKey();
return false;
}
return true;
}
}
/**
*
*/
static enum PredicateStatus {
UNKNOWN,
/*
* It's definitely not a start/stop pred. Such a pred may be later
* determined to be an index-filtering one.
*/
NOT_STARTSTOP,
/*
* It is a potentially start/stop pred
*/
STARTSTOP,
/*
* It's an index filtering pred
*/
FILTERING,
/*
* Definitely not sargable
*/
SKIP,
/*
* The pred is always false. This makes the whole WHERE expr always
* false.
*/
FALSE,
/*
* The pred is always true, so it can be removed from the WHERE expr.
*/
TRUE
}
IndexAnalyzer(
ExprSFW sfw,
ExprBaseTable tableExpr,
int tablePos,
IndexImpl index) {
theQCB = sfw.getQCB();
theSctx = sfw.getSctx();
theSFW = sfw;
theTableExpr = tableExpr;
theTablePos = tablePos;
theTable = tableExpr.getTable(tablePos);
theTargetTablePos = tableExpr.getTargetTablePos();
theIndex = index;
theIsPrimary = (theIndex == null);
int pkStartPos = 0;
int pkSize = theTable.getPrimaryKeySize();
if (!theIsPrimary) {
theNumFields = theIndex.numFields() + pkSize;
pkStartPos = theIndex.numFields();
theIndexPaths = new ArrayList(theNumFields);
theIndexPaths.addAll(theIndex.getIndexFields());
} else {
theNumFields = pkSize;
theIndexPaths = new ArrayList(theNumFields);
}
List pkColumnNames = theTable.getPrimaryKeyInternal();
for (int i = 0; i < pkSize; ++i) {
String name = pkColumnNames.get(i);
IndexField ipath = new IndexField(theTable, name, null,
i + pkStartPos);
ipath.setType(theTable.getPrimKeyColumnDef(i));
ipath.setNullable(false);
theIndexPaths.add(ipath);
}
theIsHintIndex = theTableExpr.isIndexHint(theIndex);
theWherePreds = new ArrayList(32);
thePredGroups = new ArrayList(32);
theStartStopPreds = new ArrayList>(theNumFields);
theFilteringPreds = new ArrayList();
theExprRewriteMap = new HashMap>();
for (int i = 0; i < theNumFields; ++i) {
theStartStopPreds.add(null);
}
if (theIsPrimary) {
thePrimaryKeys = new ArrayList(1);
thePrimaryKeys.add(theTable.createPrimaryKey());
theIsMapBothIndex = false;
} else {
theSecondaryKeys = new ArrayList(1);
theSecondaryKeys.add(theIndex.createIndexKey());
theIsMapBothIndex = theIndex.isMapBothIndex();
}
theRanges = new ArrayList(1);
theRanges.add(null);
thePushedExternals = new ArrayList();
}
//@SuppressWarnings("unused")
private String getIndexName() {
return (theIsPrimary ? "primary" : theIndex.getName());
}
ArrayList getPrimaryKeys() {
return thePrimaryKeys;
}
boolean hasShardKey() {
return (theIsPrimary &&
thePrimaryKeys.size() == 1 &&
thePrimaryKeys.get(0).hasShardKey());
}
private PredGroup addPredGroup(PredInfo pi) {
PredGroup pg = new PredGroup(thePredGroups.size(), pi);
thePredGroups.add(pg);
return pg;
}
private void addStartStopPred(PredInfo pi) {
int ipos = pi.theIPathPos;
ArrayList startstopPIs = theStartStopPreds.get(ipos);
if (startstopPIs == null) {
startstopPIs = new ArrayList();
theStartStopPreds.set(ipos, startstopPIs);
}
if (theTrace >= 1) {
System.out.println(
"Added startstop pred at pos " + ipos + " pred = \n" + pi);
}
startstopPIs.add(pi);
}
/**
* Remove a pred from the WHERE clause. The pred has either been
* pushed in the index or is always true.
*/
private void removePred(Expr pred) {
if (pred == null) {
return;
}
int numParents = pred.getNumParents();
if (numParents == 0) {
return;
}
Expr parent = pred.getParent(0);
if (numParents > 1) {
/*
* It's possible for the pred to have a second parent, if it is
* a non-matched index-filtering pred that has been added to the
* base table expr.
*/
if (numParents != 2 ||
(pred.getParent(1).getKind() != ExprKind.BASE_TABLE &&
pred.getParent(1).getFunction(FuncCode.OP_AND) == null)) {
throw new QueryStateException(
"Trying to remove a pred with more than one parents. pred:\n" +
pred.display() + "\nnum parents = " + numParents +
" 2nd parent:\n" + pred.getParent(1).display());
}
}
Expr whereExpr = theSFW.getWhereExpr();
if (pred == whereExpr) {
theSFW.removeWhereExpr(true/*destroy*/);
} else {
parent.removeChild(pred, true/*destroy*/);
if (parent == whereExpr && whereExpr.getNumChildren() == 0) {
theSFW.removeWhereExpr(true /*destroy*/);
}
}
}
/*
* The whole WHERE expr was found to be always false. Replace the
* whole SFW expr with an empty expr.
*/
private void processAlwaysFalse() {
Function empty = Function.getFunction(FuncCode.FN_SEQ_CONCAT);
Expr emptyExpr = ExprFuncCall.create(theQCB, theSctx,
theSFW.getLocation(),
empty,
new ArrayList());
if (theQCB.getRootExpr() == theSFW) {
theQCB.setRootExpr(emptyExpr);
} else {
theSFW.replace(emptyExpr, true);
}
theSFW = null;
}
/**
* Used to sort the IndexAnalyzers in decreasing "value" order, where
* "value" is a heuristic estimate of how effective the associated
* index is going to be in optimizing the query.
*/
@Override
public int compareTo(IndexAnalyzer other) {
int numFields1 = theNumFields;
int numFields2 = other.theNumFields;
boolean multiKey1 = (theIsPrimary ? false : theIndex.isMultiKey());
boolean multiKey2 = (other.theIsPrimary ?
false :
other.theIndex.isMultiKey());
/* Make sure the index scores are computed */
getScore();
other.getScore();
if (theTrace >= 2) {
System.out.println(
"Comparing indexes " + getIndexName() + " and " +
other.getIndexName() + "\nscore1 = " + theScore +
" score2 = " + other.theScore);
}
/*
* If one of the indexes is covering, ....
*/
if (theIsCovering != other.theIsCovering) {
if (theIsCovering) {
/*
* If the other is a preferred index, choose the covering
* index if it has at least one eq start/stop condition
* or 2 range start/stop conditions.
*/
if (!theIsHintIndex && other.theIsHintIndex) {
FieldRange range = theRanges.get(0);
return (theNumEqPredsPushed > 0 ||
(range != null &&
range.getStart() != null &&
range.getEnd() != null) ?
-1 : 1);
}
/*
* If the other index does not have a complete key, choose
* the covering index.
*/
if (other.theScore != Integer.MAX_VALUE) {
return -1;
}
/*
* The other index has a complete key. Choose the covering
* index if its score is >= to the score of the other index
* without taking into account the key completeness.
*/
return (theScore >= other.theScore2 ? -1 : 1);
}
if (other.theIsCovering) {
if (!other.theIsHintIndex && theIsHintIndex) {
FieldRange range = theRanges.get(0);
return (other.theNumEqPredsPushed > 0 ||
(range != null &&
range.getStart() != null &&
range.getEnd() != null) ?
1 : -1);
}
if (theScore != Integer.MAX_VALUE) {
return 1;
}
return (other.theScore >= theScore2 ? 1 : -1);
}
}
if (theScore == other.theScore) {
/*
* If none of the indexes has any predicates pushed and one of
* them is the primary index, choose that one.
*/
if (theScore == 0) {
if (theIsPrimary || other.theIsPrimary) {
return (theIsPrimary ? -1 : 1);
}
if (multiKey1 != multiKey2) {
return (multiKey1 ? 1 : -1);
}
}
/*
* If one of the indexes is specified in a hint, choose that
* one.
*/
if (theIsHintIndex != other.theIsHintIndex) {
return (theIsHintIndex ? -1 : 1);
}
/*
* If one of the indexes is multi-key and other simple, choose
* the simple one.
*/
if (multiKey1 != multiKey2) {
return (multiKey1 ? 1 : -1);
}
/*
* If one of the indexes is the primary index, choose that one.
*/
if (theIsPrimary || other.theIsPrimary) {
return (theIsPrimary ? -1 : 1);
}
/*
* Choose the index with the smaller number of fields. This is
* based on the assumption that if the same number of preds are
* pushed to both indexes, the more fields the index has the less
* selective the pushed predicates are going to be.
*/
if (numFields1 != numFields2) {
return (numFields1 < numFields2 ? -1 : 1);
}
/*
* TODO ???? Return the one with the smaller key size
*/
return 0;
}
/*
* If we have a complete key for one of the indexes, choose that
* one.
*/
if (theScore == Integer.MAX_VALUE ||
other.theScore == Integer.MAX_VALUE) {
return (theScore == Integer.MAX_VALUE ? -1 : 1);
}
/*
* If one of the indexes is specified in a hint, choose that one.
*/
if (theIsHintIndex != other.theIsHintIndex) {
return (theIsHintIndex ? -1 : 1);
}
return (theScore > other.theScore ? -1 : 1);
}
/**
* Computes the "score" of an index w.r.t. this query, if not done
* already.
*
* Score is a crude estimate of how effective the index is going to
* be in optimizing table access. Score is only a relative metric,
* i.e., it doesn't estimate any real metric (e.g. selectivity), but
* it is meant to be used only in comparing the relative value of two
* indexes in order to choose the "best" among all applicable indexes.
*
* Score is an integer computed as a weighted sum of the predicates
* that can be pushed into an index scan (as start/stop conditions or
* filtering preds). However, if there is a complete key for an index,
* that index gets the highest score (Integer.MAX_VALUE).
*/
private int getScore() {
if (theScore >= 0) {
return theScore;
}
theScore = 0;
theScore2 = 0;
theScore += theNumEqPredsPushed * eqValue;
FieldRange range = theRanges.get(0);
if (range != null) {
if (range.getStart() != null) {
theScore += (theIsMultiKeyRange ? arangeValue : vrangeValue);
}
if (range.getEnd() != null) {
theScore += (theIsMultiKeyRange ? arangeValue : vrangeValue);
}
}
for (PredInfo pi : theFilteringPreds) {
if (pi.isEq()) {
theScore += filterEqValue;
} else {
theScore += filterOtherValue;
}
}
theScore2 = theScore;
if (theTrace >= 2) {
System.out.println(
"Score for index " + getIndexName() + " = " + theScore +
"\ntheNumEqPredsPushed = " + theNumEqPredsPushed);
}
int numPrimKeyCols = theTable.getPrimaryKeySize();
int numFields = (theIsPrimary ?
numPrimKeyCols :
theNumFields - numPrimKeyCols);
if (theNumEqPredsPushed == numFields) {
theScore = Integer.MAX_VALUE;
return theScore;
}
return theScore;
}
/**
* The index has been chosen among the applicable indexes, so do the
* actual pred pushdown and remove all the pushed preds from the
* where clause.
*/
void apply() {
/* Collect info to be used later to eliminate unused FROM vars */
int numFroms = theSFW.getNumFroms();
int[] varRefsCounts = new int[numFroms];
for (int i = 0; i < numFroms; ++i) {
FromClause fc = theSFW.getFromClause(i);
/* Table row variables can not be eliminated ever */
if (fc.getTargetTable() != null) {
varRefsCounts[i] = 0;
} else {
varRefsCounts[i] = fc.getVar().getNumParents();
}
}
/* Add the scan boundaries to theTableExpr */
if (theIsPrimary) {
assert(theRanges.size() == thePrimaryKeys.size());
theTableExpr.addPrimaryKeys(theTablePos,
thePrimaryKeys,
theRanges,
theIsCovering);
theQCB.setPushedPrimaryKey(thePrimaryKeys.get(0));
} else if (theIndex.isGeoIndex()) {
IndexKeyImpl ikey = theSecondaryKeys.get(0);
int geoFieldPos = theIndex.getGeoFieldPos();
ArrayList geoPreds = theStartStopPreds.get(geoFieldPos);
if (geoPreds != null && !geoPreds.isEmpty()) {
assert(theSecondaryKeys.size() == 1);
assert(geoPreds.size() == 1);
PredInfo geopi = geoPreds.get(0);
if (geopi.theGeom != null &&
(!geopi.isNear() || geopi.theDistance > 0)) {
List> georanges =
CompilerAPI.getGeoUtils().ranges(geopi.theGeom,
geopi.theDistance,
theQCB.getOptions());
List geokeys = null;
if (theIndex.isGeometryIndex()) {
geokeys = CompilerAPI.getGeoUtils().keys(georanges);
}
theRanges.clear();
String pathName = theIndex.getFieldName(geoFieldPos);
FieldDefImpl rangeDef = FieldDefImpl.stringDef;
for (int i = 0; i < georanges.size(); ++i) {
Pair range = georanges.get(i);
FieldRange frange = new FieldRange(pathName, rangeDef, 0);
String start = range.first();
String end = range.second();
frange.setStart(start, true);
frange.setEnd(end, true);
theRanges.add(frange);
if (i > 0) {
theSecondaryKeys.add(ikey);
}
}
if (geokeys != null && !geokeys.isEmpty()) {
for (String key : geokeys) {
ikey = ikey.clone();
ikey.put(geoFieldPos, key);
theSecondaryKeys.add(ikey);
theRanges.add(null);
}
}
thePushedExternals.add(null);
thePushedExternals.add(null);
} else if (geopi.isNear()) {
thePushedExternals.add(geopi.theConstArg);
thePushedExternals.add(geopi.theDistanceArg);
theHavePushedExternals = true;
} else {
thePushedExternals.add(geopi.theConstArg);
thePushedExternals.add(null);
theHavePushedExternals = true;
}
} else {
assert(theRanges.size() == theSecondaryKeys.size());
}
theTableExpr.addSecondaryKeys(theTablePos,
theSecondaryKeys,
theRanges,
theIsCovering);
} else {
assert(theRanges.size() == theSecondaryKeys.size());
theTableExpr.addSecondaryKeys(theTablePos,
theSecondaryKeys,
theRanges,
theIsCovering);
}
if (theHavePushedExternals) {
theTableExpr.setPushedExternals(thePushedExternals);
}
ExprVar idxVar = null;
if (theIndex != null &&
(!theFilteringPreds.isEmpty() || theIsCovering)) {
idxVar = theSFW.addIndexVar(theTable, theIndex);
}
/*
* Add the filtering preds to theTableExpr. If a secondary index is
* used, rewrite the pred to reference the columns of the current
* index row, instead of the row columns. No rewrite is necessary for
* the primary index, because in this case the "index row" is actualy
* a table row (RowImpl) that is populated with the prim key columns
* only.
*/
int numFilteringPreds = theFilteringPreds.size();
ArrayList args = new ArrayList(numFilteringPreds);
for (PredInfo pi : theFilteringPreds) {
Expr pred = pi.thePred;
if (theIndex != null) {
pred = rewritePred(idxVar, pi);
}
args.add(pred);
}
if (args.size() > 1) {
FunctionLib fnlib = CompilerAPI.getFuncLib();
Function andFunc = fnlib.getFunc(FuncCode.OP_AND);
Expr pred = ExprFuncCall.create(theQCB, theSctx,
theTableExpr.getLocation(),
andFunc,
args);
theTableExpr.setTablePred(theTablePos, pred, false);
} else if (args.size() == 1) {
theTableExpr.setTablePred(theTablePos, args.get(0), false);
}
if (theSFW.getNumFroms() > 1) {
theEliminateDupsAtServer = true;
}
if (theEliminateDups) {
theTableExpr.setEliminateIndexDups(theEliminateDupsAtServer);
}
/*
* If possible, remove STARTSTOP, FILTERING and TRUE preds from the
* WHERE clause.
*/
for (WherePredInfo wpi : theWherePreds) {
if (wpi.isFullyPushable()) {
removePred(wpi.thePred);
continue;
}
ArrayList predinfos = wpi.thePredInfos;
for (PredInfo pi : predinfos) {
switch (pi.theStatus) {
case STARTSTOP:
case FILTERING:
/*
* If a multikey/existential pred P is pushed to the index,
* but there are other multikey preds that are not pushed,
* then P cannot be removed; it must be reapplied to the
* row selected by the index scan. For example, consider
* the following query, index, and document:
*
* create index idx_children_values on foo (
* info.children.values().age as long,
* info.children.values().school as string)
*
* select id
* from foo f
* where exists f.info.children.values($key = "Anna" and
* $value.age <= 10)
*
* {
* "id":5,
* "info":
* {
* "firstName":"first5", "lastName":"last5","age":11,
* "children":
* {
* "Anna" : { "age" : 29, "school" : "sch_1"},
* "Mark" : { "age" : 14, "school" : "sch_2"},
* "Dave" : { "age" : 16 },
* "Tim" : { "age" : 8, "school" : "sch_2"},
* "Julie" : { "age" : 12, "school" : "sch_2"}
* }
* }
* }
*
* The range pred on age is pushed to the index. The above
* doc will be selected by the index scan (because of
* "Tim"). However, the age and $key preds must both be
* applied together on each entry of the children map.
* If we remove the age pred from the WHERE clause, the
* above doc will be wrongly returned.
*
* It appears that a better rule would be to remove a
* pred factor if it is in a pred group where all the
* pred factors are alos being pushed to the index. However,
* our pred factor grouping is not very accurate, so we
* may have 2 pred factors that belong to different groups
* but should be in the same group instead.
*/
if (pi.canBeRemoved()) {
removePred(pi.thePred);
}
break;
case TRUE:
removePred(pi.thePred);
break;
case NOT_STARTSTOP:
case SKIP:
break;
default:
throw new QueryStateException(
"Unexpected state for predicate:\n" + pi);
}
}
}
/*
* If a covering secondary index is used, rewrite the SELECT and
* ORDER BY exprs to reference the columns of the current index row,
* instead of the row columns. Futhermore, if theTableExpr has nested
* tables, the WHERE clause may have preds that reference both target
* and non-target table columns, and as a result, these preds have not
* been pushed to the index. Therefore, we must rewrite the sub-exprs
* that reference only target-table columns.
*/
if (theIndex != null && theIsCovering) {
if (theTableExpr.hasNestedTables()) {
for (WherePredInfo wpi : theWherePreds) {
if (wpi.isFullyPushable()) {
continue;
}
rewriteExpr(idxVar, wpi.thePred);
}
}
int numFields = theSFW.getNumFields();
for (int i = 0; i < numFields; ++i) {
Expr expr = theSFW.getFieldExpr(i);
/*
* If the select expr is a row var, we must convert it to a
* number of step exprs that extract the column values from
* the index entry. If the row var is the only expr in the
* select, the step exprs become the new exprs of the select
* list. If not (for example select * from NESTED TABLES(...))
* we replace the row var with a record constructor whose field
* exprs are the step exprs.
*/
if (expr.getKind() == ExprKind.VAR &&
((ExprVar)expr).getTable() != null &&
((ExprVar)expr).getTable().getId() == theTable.getId()) {
RecordDefImpl rowDef = theTable.getRowDef();
int numCols = rowDef.getNumFields();
ArrayList newFieldExprs = new ArrayList(numCols);
for (int j = 0; j < numCols; ++j) {
String colName = rowDef.getFieldName(j);
int k = 0;
for (; k < theNumFields; ++k) {
IndexField ipath = theIndexPaths.get(k);
if (ipath.numSteps() == 1 &&
ipath.getStep(0).equalsIgnoreCase(colName)) {
break;
}
}
if (k == theNumFields) {
throw new QueryStateException(
"Column " + colName + " is not indexed by " +
"index " + theIndex.getName());
}
newFieldExprs.add(new ExprFieldStep(expr.getQCB(),
expr.getSctx(),
expr.getLocation(),
idxVar,
k));
}
if (numFields == 1) {
for (int j = 0; j < numCols; ++j) {
theSFW.addField(rowDef.getFieldName(j),
newFieldExprs.get(j));
}
theSFW.removeField(i, true);
} else {
Expr newFieldExpr =
new ExprRecConstr(expr.getQCB(),
expr.getSctx(),
expr.getLocation(),
rowDef,
newFieldExprs);
theSFW.setFieldExpr(i, newFieldExpr, true);
}
} else {
rewriteExpr(idxVar, expr);
}
}
int numSortExprs = theSFW.getNumSortExprs();
for (int i = 0; i < numSortExprs; ++i) {
Expr expr = theSFW.getSortExpr(i);
rewriteExpr(idxVar, expr);
}
int numTables = theTableExpr.getNumTables();
for (int i = 0; i < numTables; ++i) {
if (i == theTargetTablePos) {
continue;
}
Expr pred = theTableExpr.getTablePred(i);
if (pred != null) {
rewriteExpr(idxVar, pred);
}
}
}
/*
* Remove unused variables. If a var is not used anywhere, it can be
* removed from the FROM clause if:
* (a) Its domain expr is scalar, else
* (b) It was used before applying this index, but is not used after.
* This means that all uses of the variable were pushed down to
* index. However, if the var ranges over a table, it should not
* be removed.
*/
for (int i = numFroms - 1; i >= 0; --i) {
FromClause fc = theSFW.getFromClause(i);
if (fc.getTargetTable() != null) {
continue;
}
ExprVar var = fc.getVar();
if (var.getNumParents() == 0) {
if(theSFW.getDomainExpr(i).isScalar()) {
theSFW.removeFromClause(i, true);
} else if (varRefsCounts[i] != 0) {
if (theSFW.getDomainExpr(i).isMultiValued() &&
(theIndex == null || !theIndex.isMultiKey())) {
throw new QueryStateException(
"Attempt to remove a multi-valued variable when " +
"a non-multikey index is being applied.\n" +
"var name = " + var.getName() + " index name = " +
theIndex.getName());
}
assert(var.getTable() == null);
theSFW.removeFromClause(i, true);
}
}
}
/*
* The non-target tables are always accessed via the primary index.
* Check whether the primary index is a covering one for each of these
* tables.
*/
analyzeNonTargetTables();
}
Expr rewritePred(ExprVar idxVar, PredInfo pi) {
if (!pi.isMatched()) {
rewriteExpr(idxVar, pi.thePred);
return pi.thePred;
}
assert(pi.theEpath != null);
Location loc = pi.theEpath.theExpr.theLocation;
int fieldPos;
if (theIndex == null) {
fieldPos = theTable.getPrimKeyPos(pi.theIPathPos);
} else {
fieldPos = pi.theIPathPos;
}
ExprFieldStep idxColRef = new ExprFieldStep(theQCB, theSctx, loc,
idxVar, fieldPos);
ExprFuncCall pred = (ExprFuncCall)pi.thePred;
if (pred == null) {
assert(theIsMapBothIndex);
assert(pi.theEpath.getMapBothKey(theTable, theIndex) != null);
ExprConst keyvalExpr =
new ExprConst(theQCB, theSctx, loc, pi.theConstVal);
ArrayList args = new ArrayList(2);
args.add(idxColRef);
args.add(keyvalExpr);
pred = new ExprFuncCall(theQCB, theSctx, loc,
FuncCode.OP_EQ, args);
return pred;
}
int numArgs = pred.getNumArgs();
assert(numArgs <= 2);
ArrayList args = new ArrayList(numArgs);
for (int i = 0; i < numArgs; ++i) {
Expr arg = pred.getArg(i);
if (arg == pi.theVarArg) {
args.add(idxColRef);
} else {
args.add(arg);
}
}
pred = new ExprFuncCall(theQCB, theSctx, pred.theLocation,
pred.getFunction(null), args);
return pred;
}
void rewriteExpr(ExprVar idxVar, Expr expr) {
ArrayList exprsToReplace =
theExprRewriteMap.get(expr);
if (exprsToReplace == null) {
/* The expr does not reference any columns of theTable */
return;
}
for (ExprToReplace etr : exprsToReplace) {
int fieldPos;
if (theIndex == null) {
fieldPos = theTable.getPrimKeyPos(etr.theIndexFieldPos);
} else {
fieldPos = etr.theIndexFieldPos;
}
ExprFieldStep idxColRef = new ExprFieldStep(theQCB,
theSctx,
etr.theExpr.theLocation,
idxVar,
fieldPos);
etr.theExpr.replace(idxColRef, true/*destroy*/);
}
}
void analyzeNonTargetTables() {
int numTables = theTableExpr.getNumTables();
for (int i = 0; i < numTables; ++i) {
if (i == theTargetTablePos) {
continue;
}
IndexAnalyzer analyzer =
new IndexAnalyzer(theSFW, theTableExpr, i, null/*index*/);
analyzer.analyze();
if (analyzer.theSFW == null) {
return;
}
theTableExpr.addPrimaryKeys(i, null, null, analyzer.theIsCovering);
}
/*
* TODO: Optimize the case where an ON pred is an index-only expr, but
* the primary index is not covering. We should apply the pred using
* the index row and retrieve the full table row only for the row that
* survive the filter.
*/
}
/**
* Do the work!
*
* Note: This method will set theSFW to null if it discovers that the whole
* WHERE expr is always false. If so, it will also replace the whole SFW
* expr with an empty expr. Callers of this method should check whether
* theSFW has been set to null.
*/
void analyze() {
if (theTrace >= 1) {
System.out.println(
"\nAnalysing index " + getIndexName() + "\n");
}
/*
* Analyze WHERE preds and collect start/stop and filtering preds for
* the index. We do this even for non-target tables in a NESTED TABLES
* clause, even though WHERE preds on non-target preds cannot be pushed
* down to the index (it's not semantically correct). Nevertheless we
* do it because we need the collected info to decide if the index used
* to scan a non-target table is covering or not.
*/
Expr predsExpr = theSFW.getWhereExpr();
if (predsExpr != null) {
ArrayList preds;
Function andOp = predsExpr.getFunction(FuncCode.OP_AND);
if (andOp != null) {
preds = ((ExprFuncCall)predsExpr).getArgs();
} else {
preds = new ArrayList(1);
preds.add(predsExpr);
}
for (Expr pred : preds) {
WherePredInfo wpi = new WherePredInfo(pred);
theWherePreds.add(wpi);
collectPredInfo(wpi, pred);
if (theSFW == null) {
return;
}
}
/*
* Look for conflicting preds within each "class" of preds.
*/
for (int ipos = 0; ipos < theNumFields; ++ipos) {
boolean alwaysFalse = skipExtraneousPreds(ipos);
if (alwaysFalse) {
processAlwaysFalse();
return;
}
}
/*
* Only one of the multikey WHERE preds can be used for the index
* scan. Choose the "best" one. Also convert STARTSTOP preds to
* FILTERING ones, if needed.
*/
chooseMultiKeyPredGroup();
}
if (theTablePos == theTargetTablePos) {
/*
* Go through the preds again to collect any filtering preds that
* are not start/stop preds. We do this after choosing the best
* MapBoth key, because only path exprs using the chosen map key
* can be considered as index-only exprs.
*/
for (WherePredInfo wpi : theWherePreds) {
for (PredInfo pi : wpi.thePredInfos) {
// ????
boolean simplePathsOnly = (pi.thePredGroup != theBestPredGroup);
if (pi.theStatus == PredicateStatus.NOT_STARTSTOP &&
isIndexOnlyExpr(pi.thePred, true, simplePathsOnly)) {
pi.theStatus = PredicateStatus.FILTERING;
if (theTrace > 0) {
System.out.println(
"NOT_STARTSTOP pred converted to filtering " +
" pred:\n" +
pi.thePred.display());
}
}
}
}
pushStartStopPreds();
for (WherePredInfo wpi : theWherePreds) {
for (PredInfo pi : wpi.thePredInfos) {
if (pi.theStatus == PredicateStatus.FILTERING) {
theFilteringPreds.add(pi);
}
}
}
}
/* Check if the index is a covering one */
checkIsCovering();
}
private void collectPredInfo(WherePredInfo wpi, Expr pred) {
PredInfo pi = new PredInfo(wpi, pred);
if (pred.getKind() == ExprKind.CONST) {
ExprConst constExpr = (ExprConst)pred;
if (constExpr.getValue() == BooleanValueImpl.falseValue) {
pi.theStatus = PredicateStatus.FALSE;
processAlwaysFalse();
return;
}
if (constExpr.getValue() == BooleanValueImpl.trueValue) {
pi.theStatus = PredicateStatus.TRUE;
return;
}
}
Function func = pred.getFunction(null);
if (func == null) {
pi.theStatus = PredicateStatus.NOT_STARTSTOP;
wpi.add(pi);
return;
}
FuncCode op = func.getCode();
ExprFuncCall compExpr = (ExprFuncCall)pred;
Expr varArg;
Expr constArg;
FieldValueImpl constVal = null;
boolean isComp = func.isComparison();
boolean isExists = (op == FuncCode.OP_EXISTS);
boolean isNullOp = (op == FuncCode.OP_IS_NULL ||
op == FuncCode.OP_IS_NOT_NULL);
boolean isGeo = (op == FuncCode.FN_GEO_INTERSECT ||
op == FuncCode.FN_GEO_INSIDE ||
op == FuncCode.FN_GEO_WITHIN_DISTANCE);
if (op == FuncCode.OP_IS_NULL) {
op = FuncCode.OP_EQ;
} else if (op == FuncCode.OP_IS_NOT_NULL) {
op = FuncCode.OP_LT;
} else if (isExists) {
op = FuncCode.OP_NEQ;
} else if (isComp) {
if (func.isAnyComparison()) {
op = FuncAnyOp.anyToComp(op);
}
} else if (!isGeo) {
pi.theStatus = PredicateStatus.NOT_STARTSTOP;
wpi.add(pi);
return;
} else if (theIsPrimary || !theIndex.isGeoIndex()) {
pi.theStatus = PredicateStatus.SKIP;
wpi.add(pi);
return;
}
if (isNullOp) {
varArg = compExpr.getArg(0);
constArg = null;
constVal = NullValueImpl.getInstance();
} else if (isExists) {
varArg = compExpr.getArg(0);
constArg = null;
constVal = EmptyValueImpl.getInstance();
} else {
Expr arg0 = compExpr.getArg(0);
Expr arg1 = compExpr.getArg(1);
if (ConstKind.isConst(arg0)) {
constArg = arg0;
varArg = arg1;
if (isComp) {
op = FuncCompOp.swapCompOp(op);
}
} else if (ConstKind.isConst(arg1)) {
constArg = arg1;
varArg = arg0;
} else {
pi.theStatus = PredicateStatus.NOT_STARTSTOP;
wpi.add(pi);
// TODO: try to find path filtering preds in both args.
return;
}
if (constArg.getKind() == ExprKind.CONST) {
constVal = ((ExprConst)constArg).getValue();
} else if (ConstKind.isCompileConst(constArg)) {
List res = ExprUtils.computeConstExpr(constArg);
if (res.size() != 1) {
pi.theStatus = PredicateStatus.NOT_STARTSTOP;
wpi.add(pi);
return;
}
constVal = res.get(0);
ExprConst ge = new ExprConst(theQCB, theSctx,
constArg.getLocation(),
constVal);
constArg.replace(ge, true);
constArg = ge;
}
}
IndexExpr epath = varArg.getIndexExpr();
if (epath == null || epath.theTable.getId() != theTable.getId()) {
pi.theStatus = PredicateStatus.NOT_STARTSTOP;
wpi.add(pi);
return;
}
pi.theOp = op;
pi.theIsValueComp = func.isValueComparison();
pi.theIsExists = isExists;
pi.theVarArg = varArg;
pi.theConstArg = constArg;
pi.theConstVal = constVal;
pi.theEpath = epath;
pi.theIsGeo = isGeo;
wpi.theDoesSlicing = (wpi.theDoesSlicing || epath.theDoesSlicing);
if (isGeo) {
if (constVal != null) {
pi.theGeom = CompilerAPI.getGeoUtils().castAsGeometry(constVal);
}
if (pi.isNear()) {
pi.theDistanceArg = compExpr.getArg(2);
if (pi.theDistanceArg.getKind() == ExprKind.CONST) {
FieldValueImpl v = ((ExprConst)pi.theDistanceArg).getValue();
double dist = v.asDouble().get();
if (dist <= 0) {
processAlwaysFalse();
return;
}
pi.theDistance = dist;
}
}
}
if (func.isAnyComparison() &&
(theIsPrimary || !theIndex.isMultiKey())) {
pi.theStatus = PredicateStatus.SKIP;
}
if (isGeo &&
(theIsPrimary ||
(theIndex.isMultiKey() && theIndex.isGeometryIndex()))) {
pi.theStatus = PredicateStatus.SKIP;
}
if (op == FuncCode.OP_NEQ && !isExists) {
pi.theStatus = PredicateStatus.NOT_STARTSTOP;
}
boolean added = wpi.add(pi);
if (added) {
matchPred(pi);
}
if (pi.theStatus == PredicateStatus.FALSE) {
processAlwaysFalse();
return;
}
if (pi.theStatus == PredicateStatus.STARTSTOP) {
addStartStopPred(pi);
if (epath.theIsUnnested && epath.getMapBothKey() != null) {
throw new QueryStateException(
"Found a pred factor that is both unnested and has a " +
"MapBoth key. Predicate = \n" + pi.thePred.display());
}
if (epath.theIsUnnested) {
PredGroup.addUnnestedPred(this, pi);
} else if (epath.getMapBothKey() != null && theIsMapBothIndex) {
boolean found = PredGroup.addMapBothPred(this, pi);
if (!found) {
FieldValueImpl keyval = FieldDefImpl.stringDef.
createString(epath.getMapBothKey());
PredInfo keypi = new PredInfo(wpi, null);
keypi.theOp = FuncCode.OP_EQ;
keypi.theIsValueComp = true;
keypi.theConstVal = keyval;
keypi.theEpath = pi.theEpath;
keypi.theIPathPos = theIndex.getPosForKeysField();
keypi.theStatus = PredicateStatus.STARTSTOP;
wpi.add(keypi);
PredGroup.addMapBothPred(this, keypi);
addStartStopPred(keypi);
}
} else if (epath.isMultiKey()) {
PredGroup pg;
if (epath.theIsDirect || epath.getRelativeCtxVarPos() > 0) {
if (wpi.theLocalGroup == null) {
pg = addPredGroup(pi);
wpi.theLocalGroup = pg;
} else {
pg = wpi.theLocalGroup;
wpi.theLocalGroup.thePredInfos.add(pi);
pi.thePredGroup = wpi.theLocalGroup;
}
} else {
pg = addPredGroup(pi);
}
pg.theCtxVar = epath.theCtxVar;
}
}
List filteringPreds = epath.getFilteringPreds();
if (filteringPreds != null) {
for (Expr fpred : filteringPreds) {
if (pi.isUnnested() && theUnnestedGroup != null) {
boolean foundDuplicate = false;
for (PredInfo pi2 : theUnnestedGroup.thePredInfos) {
if (pi2.thePred == fpred) {
foundDuplicate = true;
break;
}
}
if (foundDuplicate) {
continue;
}
}
collectPredInfo(wpi, fpred);
}
}
}
/**
* Check if the given pred is a start/stop pred for a path of theIndex.
* If not, return null. Otherwise, build a PredInfo for it, and check
* the pred against any other start/stop preds on the same index path.
* Return the PredInfo, which includes the result of this check.
*/
private void matchPred(PredInfo pi) {
if (pi.theStatus != PredicateStatus.UNKNOWN) {
return;
}
IndexExpr epath = pi.theVarArg.getIndexExpr();
boolean matched = matchPathExprToIndexPath(theIndex, epath, false);
if (!matched) {
pi.theStatus = PredicateStatus.SKIP;
if (theTrace >= 2) {
System.out.println(
"Match failure for epath " + epath.getPathName());
}
return;
}
if (epath.theIsDirect &&
pi.theVarArg.isMultiValued() &&
pi.theIsValueComp) {
pi.theStatus = PredicateStatus.SKIP;
return;
}
/*
* A predicate on a prim-key column, which is not part of the index
* key definition, cannot be used as a start/stop pred on a secondary
* index. It can, however, be used as a filtering pred.
*/
if (!theIsPrimary &&
epath.thePrimKeyPos >= 0 &&
epath.getPathPos() >= theIndex.numFields() ) {
pi.theStatus = PredicateStatus.NOT_STARTSTOP;
return;
}
Expr constArg = pi.theConstArg;
FieldValueImpl constVal = (pi.isGeo() ? null : pi.theConstVal);
if (constArg != null && constVal != null && !constVal.isNull()) {
FieldDefImpl targetType = (epath.getJsonDeclaredType() != null ?
epath.getJsonDeclaredType() :
pi.theVarArg.getType().getDef());
FieldDefImpl constType = constVal.getDefinition();
FieldValueImpl newConstVal;
if (!TypeManager.areTypesComparable(targetType, constType)) {
if (theQCB.strictMode()) {
throw new QueryException(
"Incompatible types for comparison operator: \n" +
"Type1: " + pi.theVarArg.getType() +
"\nType2: " + pi.theConstArg.getType(),
pi.thePred.getLocation());
}
newConstVal = BooleanValueImpl.falseValue;
} else {
newConstVal = FuncCompOp.castConstInCompOp(
targetType,
epath.theIsJson, /*allowJsonNull*/
false, /* ignore nullability of varArg */
pi.theVarArg.isScalar(),
constVal,
pi.theOp,
theQCB.strictMode());
}
if (newConstVal != constVal) {
if (newConstVal == BooleanValueImpl.falseValue) {
pi.theStatus = PredicateStatus.FALSE;
return;
}
if (newConstVal == BooleanValueImpl.trueValue) {
pi.theStatus = PredicateStatus.TRUE;
return;
}
constVal = newConstVal;
pi.theConstVal = constVal;
}
}
if (constArg != null && !checkTypes(pi)) {
if (theTrace >= 2) {
System.out.println(
"Match failure due to type check for epath " +
epath.getPathName());
}
pi.theStatus = PredicateStatus.NOT_STARTSTOP;
return;
}
pi.theStatus = PredicateStatus.STARTSTOP;
pi.theIPathPos = epath.getPathPos();
if (pi.isUnnested() && !pi.getIndexPath().isMultiKey()) {
throw new QueryStateException(
"An unnested predicate matches with the non-multikey index " +
"field at position pi.theIPathPos. predicate:\n" + pi);
}
}
private boolean matchPathExprToIndexPath(
IndexImpl index,
IndexExpr epath,
boolean matchSimplePathsOnly) {
if (!epath.matchesIndex(theTable, index)) {
return false;
}
if (matchSimplePathsOnly &&
theBestPredGroup != null &&
theBestPredGroup.theMapBothKey != null &&
theBestPredGroup.theMapBothKey.equals(epath.getMapBothKey())) {
matchSimplePathsOnly = false;
}
if (epath.isMultiKey() && matchSimplePathsOnly) {
return false;
}
return true;
}
private boolean checkTypes(PredInfo pi) {
if (pi.theIsGeo) {
return true;
}
Expr varArg = pi.theVarArg;
Expr constArg = pi.theConstArg;
IndexExpr epath = pi.theVarArg.getIndexExpr();
if (!constArg.isScalar()) {
return false;
}
FieldDefImpl constType = constArg.getType().getDef();
Type constTypeCode = constType.getType();
FieldDefImpl varType;
Type varTypeCode;
boolean varIsScalar;
if (epath.getJsonDeclaredType() != null) {
varType = epath.getJsonDeclaredType();
varIsScalar = (!epath.isMultiKey() || epath.getMapBothKey() != null);
// TODO: change this when we have a specific type for json null
if (constTypeCode == Type.JSON) {
return true;
}
} else {
varType = varArg.getType().getDef();
varIsScalar = varArg.isScalar();
}
varTypeCode = varType.getType();
switch (varTypeCode) {
case INTEGER:
return (constTypeCode == Type.INTEGER ||
(varIsScalar && constTypeCode == Type.LONG));
case LONG:
return (constTypeCode == Type.LONG ||
constTypeCode == Type.INTEGER);
case FLOAT:
return (constTypeCode == Type.FLOAT ||
(varIsScalar && constTypeCode == Type.DOUBLE) ||
constTypeCode == Type.INTEGER ||
constTypeCode == Type.LONG);
case DOUBLE:
return (constTypeCode == Type.DOUBLE ||
constTypeCode == Type.FLOAT ||
constTypeCode == Type.INTEGER ||
constTypeCode == Type.LONG);
case NUMBER:
return constType.isNumeric();
case ENUM:
return (constTypeCode == Type.STRING ||
constTypeCode == Type.ENUM);
case STRING:
case BOOLEAN:
return varTypeCode == constTypeCode;
case TIMESTAMP:
return (varTypeCode == constTypeCode &&
((TimestampDefImpl)varType).getPrecision() ==
((TimestampDefImpl)constType).getPrecision());
default:
return false;
}
}
boolean skipExtraneousPreds(int ipos) {
ArrayList predinfos = theStartStopPreds.get(ipos);
if (predinfos == null) {
return false;
}
for (int i = 0; i < predinfos.size(); ++i) {
PredInfo pi1 = predinfos.get(i);
for (int j = i + 1; j < predinfos.size(); ++j) {
PredInfo pi2 = predinfos.get(j);
if (!pi1.isCompatible(pi2)) {
continue;
}
if (pi2.isExists()) {
pi2.theStatus = PredicateStatus.TRUE;
} else if (pi2.isGeo()) {
if (pi1.isExists()) {
pi1.theStatus = PredicateStatus.TRUE;
} else if (pi1.isGeo()) {
checkGeoGeo(pi1, pi2);
} else if (pi1.isEq()) {
FieldDefImpl def = pi1.theConstArg.getType().getDef();
if (def.mayBeJsonObject()) {
pi1.theStatus = PredicateStatus.SKIP;
}
return true;
} else {
return true;
}
} else if (pi2.isEq()) {
if (pi1.isExists()) {
pi1.theStatus = PredicateStatus.TRUE;
} else if (pi1.isGeo()) {
FieldDefImpl def = pi2.theConstArg.getType().getDef();
if (def.mayBeJsonObject()) {
pi2.theStatus = PredicateStatus.SKIP;
}
return true;
} else if (pi1.isEq()) {
checkEqEq(pi1, pi2);
} else if (pi1.isMin()) {
checkEqMin(pi2, pi1);
} else {
assert(pi1.isMax());
checkEqMax(pi2, pi1);
}
} else if (pi2.isMin()) {
if (pi1.isExists()) {
pi1.theStatus = PredicateStatus.TRUE;
} else if (pi1.isGeo()) {
return true;
} else if (pi1.isEq()) {
checkEqMin(pi1, pi2);
} else if (pi1.isMin()) {
checkMinMin(pi2, pi1);
} else {
assert(pi1.isMax());
checkMinMax(pi2, pi1);
}
} else {
assert(pi2.isMax());
if (pi1.isExists()) {
pi1.theStatus = PredicateStatus.TRUE;
} else if (pi1.isGeo()) {
return true;
} else if (pi1.isEq()) {
checkEqMax(pi1, pi2);
} else if (pi1.isMin()) {
checkMinMax(pi1, pi2);
} else {
assert(pi1.isMax());
checkMaxMax(pi2, pi1);
}
}
if (pi1.theStatus == PredicateStatus.FALSE ||
pi2.theStatus == PredicateStatus.FALSE) {
return true;
}
if (pi1.theStatus == PredicateStatus.TRUE ||
pi1.theStatus == PredicateStatus.SKIP ||
pi1.theStatus == PredicateStatus.FILTERING) {
predinfos.remove(i);
--i;
break;
}
if (pi2.theStatus == PredicateStatus.TRUE ||
pi2.theStatus == PredicateStatus.SKIP ||
pi2.theStatus == PredicateStatus.FILTERING) {
predinfos.remove(j);
--j;
}
}
}
return false;
}
private void checkEqEq(PredInfo p1, PredInfo p2) {
if (p1.theConstVal != null && p2.theConstVal != null) {
int cmp = FieldValueImpl.compareFieldValues(p1.theConstVal,
p2.theConstVal);
if (cmp == 0) {
p1.theStatus = PredicateStatus.TRUE;
} else {
p1.theStatus = PredicateStatus.FALSE;
p2.theStatus = PredicateStatus.FALSE;
}
} else if (p1.theConstVal != null) {
p2.theStatus = PredicateStatus.FILTERING;
} else {
p1.theStatus = PredicateStatus.FILTERING;
}
}
private void checkEqMin(PredInfo p1, PredInfo p2) {
if (p1.theConstVal != null && p2.theConstVal != null) {
int cmp = FieldValueImpl.compareFieldValues(p1.theConstVal,
p2.theConstVal);
if (cmp < 0 || (cmp == 0 && !p2.isInclusive())) {
p1.theStatus = PredicateStatus.FALSE;
p2.theStatus = PredicateStatus.FALSE;
} else {
p2.theStatus = PredicateStatus.TRUE;
}
} else {
p2.theStatus = PredicateStatus.FILTERING;
}
}
private void checkEqMax(PredInfo p1, PredInfo p2) {
if (p1.theConstVal != null && p2.theConstVal != null) {
int cmp = FieldValueImpl.compareFieldValues(p1.theConstVal,
p2.theConstVal);
if (cmp > 0 || (cmp == 0 && !p2.isInclusive())) {
p1.theStatus = PredicateStatus.FALSE;
p2.theStatus = PredicateStatus.FALSE;
} else {
p2.theStatus = PredicateStatus.TRUE;
}
} else {
p2.theStatus = PredicateStatus.FILTERING;
}
}
private void checkMinMin(PredInfo p1, PredInfo p2) {
if (p1.theConstVal != null && p2.theConstVal != null) {
int cmp = FieldValueImpl.compareFieldValues(p1.theConstVal,
p2.theConstVal);
if (cmp < 0 || (cmp == 0 && p1.isInclusive())) {
p1.theStatus = PredicateStatus.TRUE;
} else {
p2.theStatus = PredicateStatus.TRUE;
}
} else if (p1.theConstVal != null) {
p2.theStatus = PredicateStatus.FILTERING;
} else {
p1.theStatus = PredicateStatus.FILTERING;
}
}
private void checkMaxMax(PredInfo p1, PredInfo p2) {
if (p1.theConstVal != null && p2.theConstVal != null) {
int cmp = FieldValueImpl.compareFieldValues(p1.theConstVal,
p2.theConstVal);
if (cmp < 0 || (cmp == 0 && p2.isInclusive())) {
p2.theStatus = PredicateStatus.TRUE;
} else {
p1.theStatus = PredicateStatus.TRUE;
}
} else if (p1.theConstVal != null) {
p2.theStatus = PredicateStatus.FILTERING;
} else {
p1.theStatus = PredicateStatus.FILTERING;
}
}
private void checkMinMax(PredInfo p1, PredInfo p2) {
if (p1.theConstVal != null && p2.theConstVal != null) {
int cmp = p1.theConstVal.compareTo(p2.theConstVal);
if (cmp > 0 ||
(cmp == 0 && (!p2.isInclusive() || !p1.isInclusive()))) {
p1.theStatus = PredicateStatus.FALSE;
p2.theStatus = PredicateStatus.FALSE;
} else if (cmp == 0) {
p1.theOp = FuncCode.OP_EQ;
p2.theStatus = PredicateStatus.TRUE;
}
}
}
private void checkGeoGeo(PredInfo p1, PredInfo p2) {
if (p1.theGeom != null && p2.theGeom != null) {
Geometry geom1 = p1.theGeom;
Geometry geom2 = p2.theGeom;
if (p1.theOp == FuncCode.FN_GEO_INSIDE) {
if (!geom1.interact(geom2, p1.getLocation())) {
p1.theStatus = PredicateStatus.FALSE;
p2.theStatus = PredicateStatus.FALSE;
return;
}
} else if (p2.theOp == FuncCode.FN_GEO_INSIDE) {
if (!geom2.interact(geom1, p2.getLocation())) {
p1.theStatus = PredicateStatus.FALSE;
p2.theStatus = PredicateStatus.FALSE;
return;
}
}
double area1 = geom1.area(p1.getLocation());
double area2 = geom2.area(p2.getLocation());
if (area1 <= area2) {
p1.theGeom = geom1;
p2.theStatus = PredicateStatus.SKIP;
} else {
p2.theGeom = geom2;
p1.theStatus = PredicateStatus.SKIP;
}
} else if (p1.theGeom != null) {
p2.theStatus = PredicateStatus.SKIP;
} else {
p1.theStatus = PredicateStatus.SKIP;
}
}
private boolean checkAlwaysTrue(PredInfo p1, PredInfo p2) {
if (p1.theConstVal == null ||
p2.theConstVal == null ||
p1.isGeo() ||
p2.isGeo() ||
p2.theEnclosingPred.doesFiltering() ||
p2.theEnclosingPred.theDoesSlicing) {
return false;
}
int cmp = FieldValueImpl.compareFieldValues(p1.theConstVal,
p2.theConstVal);
if (p1.isEq()) {
if (p2.isEq()) {
return (cmp == 0);
}
if (p2.isMin()) {
if (cmp < 0 || (cmp == 0 && !p2.isInclusive())) {
return false;
}
return true;
}
assert(p2.isMax());
if (cmp > 0 || (cmp == 0 && !p2.isInclusive())) {
return false;
}
return true;
}
if (p1.isMin()) {
if (p2.isEq()) {
return false;
}
if (p2.isMin()) {
if (cmp < 0 || (cmp == 0 &&
p1.isInclusive() && !p2.isInclusive())) {
return false;
}
return true;
}
assert(p2.isMax());
return false;
}
if (p1.isMax()) {
if (p2.isEq()) {
return false;
}
if (p2.isMin()) {
return false;
}
assert(p2.isMax());
if (cmp < 0 || (cmp == 0 &&
(!p1.isInclusive() || p2.isInclusive()))) {
return true;
}
return false;
}
return false;
}
private void chooseMultiKeyPredGroup() {
boolean filteringOnly = false;
if (theTrace >= 1) {
System.out.println(
"Choosing multikey predicate class");
}
for (int ipos = 0; ipos < theNumFields; ++ipos) {
if (theTrace >= 1) {
System.out.println("processing ifield at pos " + ipos);
}
ArrayList predinfos = theStartStopPreds.get(ipos);
if (predinfos == null) {
if (theTrace >= 1) {
System.out.println("no preds at pos " + ipos);
}
filteringOnly = true;
continue;
}
if (theIsPrimary || !theIndexPaths.get(ipos).isMultiKey()) {
if (predinfos.size() > 2) {
throw new QueryStateException(
"More than two predicates for non-multikey index " +
"field at position " + ipos);
}
if (!predinfos.get(0).isEq()) {
if (theTrace >= 1) {
System.out.println("no EQ preds at pos " + ipos);
}
filteringOnly = true;
}
continue;
}
for (int i = 0; i < predinfos.size(); ++i) {
PredInfo pi = predinfos.get(i);
PredGroup pg = pi.thePredGroup;
if (theTrace >= 1) {
System.out.println(
"processing pred at pos " + ipos + "\nPG: " +
pg.theId + " filtering = " + pg.theFilteringOnly +
"\nMapBothKey = " + pg.theMapBothKey + "\nPred = \n" +
pi);
}
if (pi.theStatus != PredicateStatus.STARTSTOP) {
throw new QueryStateException(
"Found a non STARTSTOP predicate in theStartStopPreds " +
" as position " + ipos);
}
if (filteringOnly || pg.theFilteringOnly) {
if (pi.isEq()) {
pg.theFieldScore += filterEqValue;
} else {
pg.theFieldScore += filterOtherValue;
}
} else if (pi.isEq()) {
pg.theFieldScore += eqValue;
} else {
pg.theFieldScore += vrangeValue;
pg.theFoundRange = true;
}
if (theTrace >= 1) {
System.out.println("Field score = " + pg.theFieldScore);
}
}
for (PredGroup pg : thePredGroups) {
pg.theScore += pg.theFieldScore;
if (theTrace >= 1) {
System.out.println("Total score for PG " + pg.theId +
" = " + pg.theScore);
}
if (pg.theFieldScore == 0 || pg.theFoundRange) {
pg.theFilteringOnly = true;
}
pg.theFieldScore = 0;
}
}
/*
* Now choose the "best" multikey WHERE pred
*/
for (PredGroup pg : thePredGroups) {
if (theBestPredGroup == null) {
if (pg.theScore > 0) {
theBestPredGroup = pg;
}
} else if (pg.theScore > theBestPredGroup.theScore) {
theBestPredGroup = pg;
} else if (pg.theScore == theBestPredGroup.theScore) {
if (!pg.theIsUnnested && theBestPredGroup.theIsUnnested ||
pg.theId < theBestPredGroup.theId) {
theBestPredGroup = pg;
}
}
}
if (theBestPredGroup == null) {
return;
}
if (theIndex != null && theIndex.isMultiKeyMapIndex()) {
for (PredInfo pi : theBestPredGroup.thePredInfos) {
if (pi.theIPathPos >= 0) {
IndexField ipath = theIndexPaths.get(pi.theIPathPos);
if (pi.mapBothKey() != null ||
(pi.isEq() && ipath.isMapKeys())) {
theHaveMapKeyEqPred = true;
break;
}
}
}
}
if (theTrace >= 1) {
System.out.println(
"Best pred group = \n" + theBestPredGroup.theId);
}
/*
* Remove from the multikey index paths in theStartStopPreds all pred
* infos that do not belong to theBestMultiKeyPred, and mark them as SKIP.
*/
for (int i = 0; i < theNumFields; ++i) {
if (!theIndexPaths.get(i).isMultiKey()) {
continue;
}
ArrayList preds = theStartStopPreds.get(i);
if (preds == null) {
continue;
}
for (int j = 0; j < preds.size(); ++j) {
PredInfo pi = preds.get(j);
if (pi.theStatus == PredicateStatus.SKIP) {
preds.remove(j);
--j;
continue;
}
if (pi.thePredGroup == theBestPredGroup) {
continue;
}
preds.remove(j);
--j;
if (pi.mapBothKey() == null) {
for (PredInfo pi2 : preds) {
if (pi2.thePredGroup == theBestPredGroup &&
(pi2.isUnnested() == pi.isUnnested() ||
pi2.isUnnested()) &&
checkAlwaysTrue(pi2, pi)) {
pi.theStatus = PredicateStatus.TRUE;
break;
}
}
}
if (pi.theStatus != PredicateStatus.TRUE) {
pi.theStatus = PredicateStatus.SKIP;
}
}
}
}
/**
* Try to push start/stop preds on the given index path.
*/
private void pushStartStopPreds() {
boolean pushedMultiKeyPred = false;
int lastStartStopPos = -2; // -2 means unknown
int ipos = 0;
for (; ipos < theNumFields; ++ipos) {
ArrayList predinfos = theStartStopPreds.get(ipos);
if (predinfos == null || predinfos.isEmpty()) {
if (lastStartStopPos == -2) {
lastStartStopPos = ipos-1;
}
continue;
}
if (predinfos.size() > 2) {
throw new QueryStateException(
"More than 2 start/stop predicates for index field at " +
"position " + ipos);
}
PredInfo pi1 = predinfos.get(0);
PredInfo pi2 = (predinfos.size() > 1 ? predinfos.get(1) : null);
if (pi1.theStatus != PredicateStatus.STARTSTOP) {
throw new QueryStateException(
"Pushing a predicate marked as " + pi1.theStatus + "\n" +
pi1);
}
if (pi2 != null && pi2.theStatus != PredicateStatus.STARTSTOP) {
throw new QueryStateException(
"Pushing a predicate marked as " + pi2.theStatus + "\n" +
pi2);
}
if (lastStartStopPos >= -1) {
pi1.theStatus = PredicateStatus.FILTERING;
if (pi2 != null) {
pi2.theStatus = PredicateStatus.FILTERING;
}
continue;
}
IndexField ipath = theIndexPaths.get(ipos);
pushedMultiKeyPred = (pushedMultiKeyPred || ipath.isMultiKey());
if (pi1.isEq()) {
assert(predinfos.size() == 1);
assert(!ipath.isMapKeys() || theHaveMapKeyEqPred);
FieldValueImpl constVal;
if (pi1.theConstVal != null) {
thePushedExternals.add(null);
constVal = pi1.theConstVal;
} else {
theHavePushedExternals = true;
thePushedExternals.add(pi1.theConstArg);
constVal = createPlaceHolderValue(ipath.getType());
}
if (theIsPrimary) {
thePrimaryKeys.get(0).put(ipath.getStep(0), constVal);
} else {
theSecondaryKeys.get(0).put(ipos, constVal);
}
++theNumEqPredsPushed;
continue;
}
if (pi1.isExists()) {
assert(predinfos.size() == 1);
assert(!theIsPrimary);
theSecondaryKeys.add(theSecondaryKeys.get(0));
String pathName = theIndex.getFieldName(ipos);
FieldDefImpl rangeDef = ipath.getType();
FieldRange fr1 = new FieldRange(pathName, rangeDef, 0);
FieldRange fr2 = new FieldRange(pathName, rangeDef, 0);
fr1.setEnd(EmptyValueImpl.getInstance(), false, false);
fr2.setStart(EmptyValueImpl.getInstance(), false, false);
theRanges.set(0, fr1);
theRanges.add(fr2);
thePushedExternals.add(null);
if (ipath.isMultiKey() && !theHaveMapKeyEqPred) {
theIsMultiKeyRange = true;
theEliminateDups = true;
}
lastStartStopPos = ipos;
continue;
}
if (pi1.isGeo()) {
lastStartStopPos = ipos;
String pathName = theIndex.getFieldName(ipos);
FieldDefImpl rangeDef = FieldDefImpl.stringDef;
FieldRange fr = new FieldRange(pathName, rangeDef, 0);
fr.setEnd(EmptyValueImpl.getInstance(), false, false);
fr.setStart(EmptyValueImpl.getInstance(), false, false);
theRanges.set(0, fr);
if (ipath.isMultiKey() || theIndex.isGeometryIndex()) {
theIsMultiKeyRange = true;
theEliminateDups = true;
}
if (theTrace >= 2) {
System.out.println("Added fake range for geo pred");
}
continue;
}
PredInfo minpi = null;
PredInfo maxpi = null;
if (pi1.isMin()) {
minpi = pi1;
if (pi2 != null) {
assert(pi2.isMax());
maxpi = pi2;
}
} else {
assert(pi1.isMax());
maxpi = pi1;
if (pi2 != null) {
assert(pi2.isMin());
minpi = pi2;
}
}
createRange(ipath, minpi, maxpi);
if (theIsMultiKeyRange) {
theEliminateDups = true;
}
lastStartStopPos = ipos;
}
if (theIndex != null &&
theIndex.isMultiKey() &&
!theHaveMapKeyEqPred &&
!theEliminateDups) {
if (!pushedMultiKeyPred) {
theEliminateDups = true;
} else {
for (ipos = lastStartStopPos+1;
ipos < theIndexPaths.size();
++ipos) {
IndexField ipath = theIndexPaths.get(ipos);
//System.out.println("Path at pos " + ipos +
// " isMultiKey = " + ipath.isMultiKey());
if (ipath.isMultiKey()) {
theEliminateDups = true;
break;
}
}
}
}
//System.out.println("theEliminateDups for index " + getIndexName() +
// " = " + theEliminateDups);
}
/**
*
*/
private void createRange(IndexField ipath, PredInfo minpi, PredInfo maxpi) {
int storageSize = (theIsPrimary ?
theTable.getPrimaryKeySize(ipath.getStep(0)) :
0);
FieldDefImpl rangeDef = ipath.getType();
String pathName = (theIsPrimary ? ipath.getStep(0) :
theIndex.getFieldName(ipath.getPosition()));
FieldRange range = new FieldRange(pathName, rangeDef, storageSize);
theRanges.set(0, range);
if (minpi != null) {
if (minpi.theConstVal == null) {
theHavePushedExternals = true;
thePushedExternals.add(minpi.theConstArg);
FieldValueImpl val = createPlaceHolderValue(rangeDef);
range.setStart(val, minpi.isInclusive(), false);
} else {
thePushedExternals.add(null);
range.setStart(minpi.theConstVal, minpi.isInclusive());
}
if (ipath.isMultiKey() && !theHaveMapKeyEqPred) {
theIsMultiKeyRange = true;
}
} else {
thePushedExternals.add(null);
}
if (maxpi != null) {
if (maxpi.theConstVal == null) {
theHavePushedExternals = true;
thePushedExternals.add(maxpi.theConstArg);
FieldValueImpl val = createPlaceHolderValue(rangeDef);
range.setEnd(val, maxpi.isInclusive(), false);
} else {
thePushedExternals.add(null);
range.setEnd(maxpi.theConstVal, maxpi.isInclusive());
}
if (ipath.isMultiKey() && !theHaveMapKeyEqPred) {
theIsMultiKeyRange = true;
}
} else {
thePushedExternals.add(null);
}
}
/*
* Check if the index is a covering one. For this to be true, the index
* must "cover" all the exprs in the query. We say that the index covers
* an expr if the expr does not reference any non-indexed paths within
* theTable. If the query does not have a NESTED TABLES, this means that
* the whole expr can be evaluated using index fields only.
*/
private void checkIsCovering() {
int numPreds = getNumPreds();
int numIndexPreds = 0;
if (theSFW == null) {
theIsCovering = false;
return;
}
/*
* Any index of key-only table is always covering. Nevertheless, we
* must still go through all the exprs and call isIndexOnlyExpr on
* them, because isIndexOnlyExpr() creates theExprRewriteMap.
*/
boolean isKeyOnly = (theSFW != null && theTable.isKeyOnly());
if (isKeyOnly) {
assert(theIsPrimary || !theIndex.isMultiKey());
assert(!theSFW.hasSort() ||
(theSFW.hasPrimaryIndexBasedSort() && theIsPrimary) ||
(theSFW.getSortingIndexes().contains(theIndex)));
}
boolean hasNestedTables = theTableExpr.hasNestedTables();
/*
* Check whether the index covers all the WHERE preds.
*/
for (WherePredInfo wpi : theWherePreds) {
if (wpi.isFullyPushable()) {
++numIndexPreds;
continue;
}
if (hasNestedTables &&
isIndexOnlyExpr(wpi.thePred, false, true)) {
++numIndexPreds;
}
}
assert(numIndexPreds <= numPreds);
theIsCovering = (numIndexPreds == numPreds);
if (!theIsCovering) {
assert(!isKeyOnly);
return;
}
/*
* Check whether the index covers all the exprs in the SELECT clause.
*/
int numFieldExprs = theSFW.getNumFields();
for (int i = 0; i < numFieldExprs; ++i) {
Expr expr = theSFW.getFieldExpr(i);
if (!isIndexOnlyExpr(expr, false, true)) {
/*
* If the expr is a row var, see if every column of the table
* is contained in the index entry.
*/
if ((theIsPrimary || !theIndex.isMultiKey()) &&
expr.getKind() == ExprKind.VAR &&
((ExprVar)expr).getTable() != null &&
((ExprVar)expr).getTable().getId() == theTable.getId()) {
RecordDefImpl rowDef = theTable.getRowDef();
int numCols = rowDef.getNumFields();
int j = 0;
for (; j < numCols; ++j) {
String colName = rowDef.getFieldName(j);
int k = 0;
for (; k < theNumFields; ++k) {
IndexField ipath = theIndexPaths.get(k);
if (ipath.numSteps() == 1 &&
ipath.getStep(0).equalsIgnoreCase(colName)) {
break;
}
}
if (k == theNumFields) {
break;
}
}
if (j == numCols) {
continue;
}
}
} else {
continue;
}
theIsCovering = false;
return;
}
/*
* The index must cover all the exprs in the ORDERBY clause. Normally,
* this should be true already, but we must call isIndexOnlyExpr() on
* each sort expr in order to rewrite it to access the index var.
* Furthermore, the primary index is always analyzed, and if the query
* is not sorting by prim key columns, we must mark the prim index as
* not covering.
*/
if (theTablePos == theTargetTablePos) {
int numSortExprs = theSFW.getNumSortExprs();
for (int i = 0; i < numSortExprs; ++i) {
Expr expr = theSFW.getSortExpr(i);
if (!isIndexOnlyExpr(expr, true, true)) {
theIsCovering = false;
return;
}
}
}
int numFroms = theSFW.getNumFroms();
for (int i = 0; i < numFroms; ++i) {
FromClause fc = theSFW.getFromClause(i);
Expr domExpr = fc.getDomainExpr();
/*
* Check whether the index covers all the ON preds in a NESTED
* TABLES clause.
*/
if (domExpr == theTableExpr) {
if (!hasNestedTables) {
continue;
}
int numTables = theTableExpr.getNumTables();
for (int j = 0; j < numTables; ++j) {
Expr pred = theTableExpr.getTablePred(j);
if (pred != null &&
!isIndexOnlyExpr(pred, false, true)) {
theIsCovering = false;
return;
}
}
continue;
}
ExprVar var = fc.getVar();
/*
* If the var is used in any exprs, those exprs have been checked
* above. Also, if the var is not used in any exprs, but its
* domain expr is scalar, the var will be removed when we apply
* the index. So, they only case we need to check here is that
* the var is not used in any exprs and its domain is not scalar.
*/
if (var.getNumParents() == 0 && !domExpr.isScalar()) {
if (!isIndexOnlyExpr(domExpr, false, true)) {
theIsCovering = false;
assert(!isKeyOnly);
return;
}
}
}
}
/**
* If strict is true, this method checks whether the given expr is an expr
* that can be evaluated using the columns of the current index only (which
* may be the primary index, if theIndex is null). If strict is false, the
* method allows the expr to reference columns from tables other than
* theTable. So it will return true if for each subexpr of expr that
* references columns of theTable only, the subexpr can be evaluated using
* the columns of the current index entry only.
*/
private boolean isIndexOnlyExpr(
Expr expr,
boolean strict,
boolean matchSimplePathsOnly) {
return isIndexOnlyExpr(expr, expr, strict, matchSimplePathsOnly);
}
private boolean isIndexOnlyExpr(
Expr initExpr,
Expr expr,
boolean strict,
boolean matchSimplePathsOnly) {
switch (expr.getKind()) {
case FIELD_STEP:
case MAP_FILTER:
case ARRAY_FILTER:
case ARRAY_SLICE:
case VAR: {
if (expr.getKind() == ExprKind.VAR) {
ExprVar var = (ExprVar)expr;
if (var.isExternal()) {
return true;
}
}
IndexExpr epath = expr.getIndexExpr();
if (epath == null ||
epath.theDoesSlicing ||
epath.theIsGeo ||
epath.theFilteringPreds != null ||
(strict && epath.theTable.getId() != theTable.getId())) {
return false;
}
if (!strict && epath.theTable.getId() != theTable.getId()) {
return true;
}
if (matchSimplePathsOnly &&
epath.getRelativeCtxVarPos(theTable, theIndex) > 0 &&
epath.theCtxVar == theBestPredGroup.theCtxVar) {
matchSimplePathsOnly = false;
}
if (!matchPathExprToIndexPath(theIndex,
epath,
matchSimplePathsOnly)) {
return false;
}
ArrayList exprsToReplace =
theExprRewriteMap.get(initExpr);
if (exprsToReplace == null) {
exprsToReplace = new ArrayList();
theExprRewriteMap.put(initExpr, exprsToReplace);
}
exprsToReplace.add(new ExprToReplace(expr, epath.getPathPos()));
return true;
}
case BASE_TABLE:
return false;
case CONST:
return true;
case FUNC_CALL:
Function func = expr.getFunction(null);
if (func != null &&
(func.getCode() == FuncCode.FN_GEO_INTERSECT ||
func.getCode() == FuncCode.FN_GEO_INSIDE)) {
return false;
}
break;
default:
break;
}
ExprIter children = expr.getChildren();
while (children.hasNext()) {
Expr child = children.next();
if (!isIndexOnlyExpr(initExpr, child, strict,
matchSimplePathsOnly)) {
children.reset();
return false;
}
}
children.reset();
return true;
}
static FieldValueImpl createPlaceHolderValue(FieldDefImpl type) {
switch (type.getType()) {
case INTEGER:
return FieldDefImpl.integerDef.createInteger(0);
case LONG:
return FieldDefImpl.longDef.createLong(0);
case FLOAT:
return FieldDefImpl.floatDef.createFloat(0.0F);
case DOUBLE:
return FieldDefImpl.doubleDef.createDouble(0.0);
case NUMBER:
return FieldDefImpl.numberDef.createNumber(0);
case STRING:
return FieldDefImpl.stringDef.createString("");
case ENUM:
return ((EnumDefImpl)type).createEnum(1);
default:
throw new QueryStateException(
"Unexpected type for index key: " + type);
}
}
/**
* Return the number of preds in the WHERE clause of the SFW expr
*/
private int getNumPreds() {
if (theSFW == null) {
return 0;
}
Expr whereExpr = theSFW.getWhereExpr();
if (whereExpr == null) {
return 0;
}
Function andOp = whereExpr.getFunction(FuncCode.OP_AND);
if (andOp != null) {
return whereExpr.getNumChildren();
}
return 1;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy