@Override public final Expr optimize(final QueryContext qc, final VarScope scp) throws QueryException { final Value v = initial(qc); if (v != null && v.isEmpty() || emptyPath(v)) return optPre(qc); // merge descendant steps Expr e = mergeSteps(qc); if (e == this && v != null && v.type == NodeType.DOC) { // check index access e = index(qc, v); // rewrite descendant to child steps if (e == this) e = children(qc, v); } // recompile path if it has changed if (e != this) return e.compile(qc, scp); // set atomic type for single attribute steps to speed up predicate tests if (root == null && steps.length == 1 && steps[0] instanceof Step) { final Step curr = (Step) steps[0]; if (curr.axis == ATTR && curr.test.kind == Kind.URI_NAME) curr.seqType(SeqType.NOD_ZO); } // choose best path implementation and set type information final Path path = get(info, root, steps); path.size = path.size(qc); path.seqType = SeqType.get(steps[steps.length - 1].seqType().type, size); return path; }
/** * If possible, returns an expression which accesses the index. Otherwise, returns the original * expression. * * @param ctx query context * @param data data reference * @return resulting expression * @throws QueryException query exception */ private Expr index(final QueryContext ctx, final Data data) throws QueryException { // disallow relative paths and numeric predicates if (root == null || uses(Use.POS)) return this; // cache index access costs IndexContext ics = null; // cheapest predicate and step int pmin = 0; int smin = 0; // check if path can be converted to an index access for (int s = 0; s < steps.length; ++s) { // find cheapest index access final AxisStep stp = step(s); if (!stp.axis.down) break; // check if resulting index path will be duplicate free final boolean i = pathNodes(data, s) != null; // choose cheapest index access for (int p = 0; p < stp.preds.length; ++p) { final IndexContext ic = new IndexContext(ctx, data, stp, i); if (!stp.preds[p].indexAccessible(ic)) continue; if (ic.costs() == 0) { if (ic.not) { // not operator... accept all results stp.preds[p] = Bln.TRUE; continue; } // no results... ctx.compInfo(OPTNOINDEX, this); return Empty.SEQ; } if (ics == null || ics.costs() > ic.costs()) { ics = ic; pmin = p; smin = s; } } } // skip if no index access is possible, or if it is too expensive if (ics == null || ics.costs() > data.meta.size) return this; // replace expressions for index access final AxisStep stp = step(smin); final Expr ie = stp.preds[pmin].indexEquivalent(ics); if (ics.seq) { // sequential evaluation; do not invert path stp.preds[pmin] = ie; } else { // inverted path, which will be represented as predicate AxisStep[] invSteps = {}; // collect remaining predicates final Expr[] newPreds = new Expr[stp.preds.length - 1]; int c = 0; for (int p = 0; p != stp.preds.length; ++p) { if (p != pmin) newPreds[c++] = stp.preds[p]; } // check if path before index step needs to be inverted and traversed final Test test = DocTest.get(ctx, data); boolean inv = true; if (test == Test.DOC && data.meta.pathindex && data.meta.uptodate) { int j = 0; for (; j <= smin; ++j) { // invert if axis is not a child or has predicates final AxisStep s = axisStep(j); if (s == null) break; if (s.axis != Axis.CHILD || s.preds.length > 0 && j != smin) break; if (s.test.test == Name.ALL || s.test.test == null) continue; if (s.test.test != Name.NAME) break; // support only unique paths with nodes on the correct level final int name = data.tagindex.id(s.test.name.local()); final ObjList<PathNode> pn = data.paths.desc(name, Data.ELEM); if (pn.size() != 1 || pn.get(0).level() != j + 1) break; } inv = j <= smin; } // invert path before index step if (inv) { for (int j = smin; j >= 0; --j) { final Axis ax = step(j).axis.invert(); if (ax == null) break; if (j != 0) { final AxisStep prev = step(j - 1); invSteps = Array.add(invSteps, AxisStep.get(input, ax, prev.test, prev.preds)); } else { // add document test for collections and axes other than ancestors if (test != Test.DOC || ax != Axis.ANC && ax != Axis.ANCORSELF) invSteps = Array.add(invSteps, AxisStep.get(input, ax, test)); } } } // create resulting expression final AxisPath result; final boolean simple = invSteps.length == 0 && newPreds.length == 0; if (ie instanceof AxisPath) { result = (AxisPath) ie; } else if (smin + 1 < steps.length || !simple) { result = simple ? new AxisPath(input, ie) : new AxisPath(input, ie, AxisStep.get(input, Axis.SELF, Test.NOD)); } else { return ie; } // add remaining predicates to last step final int ls = result.steps.length - 1; if (ls >= 0) { result.steps[ls] = result.step(ls).addPreds(newPreds); // add inverted path as predicate to last step if (invSteps.length != 0) result.steps[ls] = result.step(ls).addPreds(Path.get(input, null, invSteps)); } // add remaining steps for (int s = smin + 1; s < steps.length; ++s) { result.steps = Array.add(result.steps, steps[s]); } return result; } return this; }
/** * Converts descendant to child steps. * * @param ctx query context * @param data data reference * @return path */ Expr children(final QueryContext ctx, final Data data) { // skip path check if no path index exists, or if it is out-of-date if (!data.meta.uptodate || data.nspaces.globalNS() == null) return this; Path path = this; for (int s = 0; s < steps.length; ++s) { // don't allow predicates in preceding location steps final Step prev = s > 0 ? axisStep(s - 1) : null; if (prev != null && prev.preds.length != 0) break; // ignore axes other than descendant, or numeric predicates final Step curr = axisStep(s); if (curr == null || curr.axis != DESC || curr.has(Flag.FCS)) continue; // check if child steps can be retrieved for current step ArrayList<PathNode> pn = pathNodes(data, s); if (pn == null) continue; // cache child steps final ArrayList<QNm> qnm = new ArrayList<>(); while (pn.get(0).par != null) { QNm nm = new QNm(data.tagindex.key(pn.get(0).name)); // skip children with prefixes if (nm.hasPrefix()) return this; for (final PathNode p : pn) { if (pn.get(0).name != p.name) nm = null; } qnm.add(nm); pn = PathSummary.parent(pn); } ctx.compInfo(OPTCHILD, steps[s]); // build new steps int ts = qnm.size(); final Expr[] stps = new Expr[ts + steps.length - s - 1]; for (int t = 0; t < ts; ++t) { final Expr[] preds = t == ts - 1 ? ((Preds) steps[s]).preds : new Expr[0]; final QNm nm = qnm.get(ts - t - 1); final NameTest nt = nm == null ? new NameTest(false) : new NameTest(nm, Mode.LN, false, null); stps[t] = Step.get(info, CHILD, nt, preds); } while (++s < steps.length) stps[ts++] = steps[s]; path = get(info, root, stps); break; } // check if the all children in the path exist; don't test with namespaces if (data.nspaces.size() == 0) { LOOP: for (int s = 0; s < path.steps.length; ++s) { // only verify child steps; ignore namespaces final Step st = path.axisStep(s); if (st == null || st.axis != CHILD) break; if (st.test.mode == Mode.ALL || st.test.mode == null) continue; if (st.test.mode != Mode.LN) break; // check if one of the addressed nodes is on the correct level final int name = data.tagindex.id(st.test.name.local()); for (final PathNode pn : data.paths.desc(name, Data.ELEM)) { if (pn.level() == s + 1) continue LOOP; } ctx.compInfo(OPTPATH, path); return Empty.SEQ; } } return path; }