/** * Computes the number of results. * * @param qc query context (may be @code null) * @return number of results */ private long size(final QueryContext qc) { final Value rt = initial(qc); // skip computation if value is not a document node if (rt == null || rt.type != NodeType.DOC) return -1; final Data data = rt.data(); // skip computation if no database instance is available, is out-of-date or // if context does not contain all database nodes if (data == null || !data.meta.uptodate || data.resources.docs().size() != rt.size()) return -1; ArrayList<PathNode> nodes = data.paths.root(); long m = 1; final int sl = steps.length; for (int s = 0; s < sl; s++) { final Step curr = axisStep(s); if (curr != null) { nodes = curr.nodes(nodes, data); if (nodes == null) return -1; } else if (s + 1 == sl) { m = steps[s].size(); } else { // stop if a non-axis step is not placed last return -1; } } long sz = 0; for (final PathNode pn : nodes) sz += pn.stats.count; return sz * m; }
/** * Checks if the expressions is a simple child step. * * @param expr expression to be checked * @return result of check */ private boolean simpleChild(final Expr expr) { if (expr instanceof Step) { final Step n = (Step) expr; if (n.axis == CHILD && !n.has(Flag.FCS)) return true; } return false; }
@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; }
/** * Converts descendant to child steps. * * @param qc query context * @param rt root value * @return original or new expression */ private Expr children(final QueryContext qc, final Value rt) { // skip if index does not exist or is out-dated, or if several namespaces occur in the input final Data data = rt.data(); if (data == null || !data.meta.uptodate || data.nspaces.globalNS() == null) return this; Path path = this; final int sl = steps.length; for (int s = 0; s < sl; 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> nodes = pathNodes(data, s); if (nodes == null) continue; // cache child steps final ArrayList<QNm> qnm = new ArrayList<>(); while (nodes.get(0).parent != null) { QNm nm = new QNm(data.elemNames.key(nodes.get(0).name)); // skip children with prefixes if (nm.hasPrefix()) return this; for (final PathNode p : nodes) { if (nodes.get(0).name != p.name) nm = null; } qnm.add(nm); nodes = PathSummary.parent(nodes); } qc.compInfo(OPTCHILD, steps[s]); // build new steps int ts = qnm.size(); final Expr[] stps = new Expr[ts + sl - 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, Kind.NAME, false, null); stps[t] = Step.get(info, CHILD, nt, preds); } while (++s < sl) stps[ts++] = steps[s]; path = get(info, root, stps); break; } // check if all steps yield results; if not, return empty sequence final ArrayList<PathNode> nodes = pathNodes(qc); if (nodes != null && nodes.isEmpty()) { qc.compInfo(OPTPATH, path); return Empty.SEQ; } return path; }
/** * Optimizes descendant-or-self steps and static types. * * @param ctx query context */ void optSteps(final QueryContext ctx) { boolean opt = false; Expr[] st = steps; for (int l = 1; l < st.length; ++l) { if (!(st[l - 1] instanceof Step && st[l] instanceof Step)) continue; final Step prev = (Step) st[l - 1]; final Step curr = (Step) st[l]; if (!prev.simple(DESCORSELF, false)) continue; if (curr.axis == CHILD && !curr.has(Flag.FCS)) { // descendant-or-self::node()/child::X -> descendant::X final int sl = st.length; final Expr[] tmp = new Expr[sl - 1]; System.arraycopy(st, 0, tmp, 0, l - 1); System.arraycopy(st, l, tmp, l - 1, sl - l); st = tmp; curr.axis = DESC; opt = true; } else if (curr.axis == ATTR && !curr.has(Flag.FCS)) { // descendant-or-self::node()/@X -> descendant-or-self::*/@X prev.test = new NameTest(false); opt = true; } } if (opt) ctx.compInfo(OPTDESC); // set atomic type for single attribute steps to speedup predicate tests if (root == null && st.length == 1 && st[0] instanceof Step) { final Step curr = (Step) st[0]; if (curr.axis == ATTR && curr.test.mode == Mode.STD) curr.type = SeqType.NOD_ZO; } steps = st; }
/** * Returns the path nodes that will result from this path. * * @param qc query context * @return path nodes, or {@code null} if nodes cannot be evaluated */ public final ArrayList<PathNode> pathNodes(final QueryContext qc) { final Value init = initial(qc); final Data data = init != null && init.type == NodeType.DOC ? init.data() : null; if (data == null || !data.meta.uptodate) return null; ArrayList<PathNode> nodes = data.paths.root(); final int sl = steps.length; for (int s = 0; s < sl; s++) { final Step curr = axisStep(s); if (curr == null) return null; nodes = curr.nodes(nodes, data); if (nodes == null) return null; } return nodes; }
/** * Merges expensive descendant-or-self::node() steps. * * @param qc query context * @return original or new expression */ private Expr mergeSteps(final QueryContext qc) { boolean opt = false; final int sl = steps.length; final ExprList stps = new ExprList(sl); for (int s = 0; s < sl; s++) { final Expr step = steps[s]; // check for simple descendants-or-self step with succeeding step if (s < sl - 1 && step instanceof Step) { final Step curr = (Step) step; if (curr.simple(DESCORSELF, false)) { // check succeeding step final Expr next = steps[s + 1]; // descendant-or-self::node()/child::X -> descendant::X if (simpleChild(next)) { ((Step) next).axis = DESC; opt = true; continue; } // descendant-or-self::node()/(X, Y) -> (descendant::X | descendant::Y) Expr e = mergeList(next); if (e != null) { steps[s + 1] = e; opt = true; continue; } // //(X, Y)[text()] -> (/descendant::X | /descendant::Y)[text()] if (next instanceof Filter && !next.has(Flag.FCS)) { final Filter f = (Filter) next; e = mergeList(f.root); if (e != null) { f.root = e; opt = true; continue; } } } } stps.add(step); } if (opt) { qc.compInfo(OPTDESC); return get(info, root, stps.finish()); } return this; }
/** * Returns a new path instance. * * @param ii input info * @param r root expression; can be a {@code null} reference * @param path path steps * @return class instance */ public static Path get(final InputInfo ii, final Expr r, final Expr... path) { // check if all steps are axis steps boolean axes = true; for (int p = 0; p < path.length; p++) { Expr e = path[p]; if (e instanceof Context) { e = Step.get(((ParseExpr) e).info, SELF, Test.NOD); } else if (e instanceof Filter) { final Filter f = (Filter) e; if (f.root instanceof Context) { e = Step.get(f.info, SELF, Test.NOD, f.preds); } } axes &= e instanceof Step; path[p] = e; } return axes ? new CachedPath(ii, r, path).finish(null) : new MixedPath(ii, r, path); }
/** * Returns a new path instance. A path implementation is chosen that works fastest for the given * steps. * * @param info input info * @param root root expression; can be {@code null} * @param steps steps * @return path instance */ public static Path get(final InputInfo info, final Expr root, final Expr... steps) { // new list with steps final ExprList stps = new ExprList(steps.length); // merge nested paths Expr rt = root; if (rt instanceof Path) { final Path path = (Path) rt; stps.add(path.steps); rt = path.root; } // remove redundant context reference if (rt instanceof Context) rt = null; // add steps of input array final int sl = steps.length; for (int s = 0; s < sl; s++) { Expr step = steps[s]; if (step instanceof Context) { // remove redundant context references if (sl > 1) continue; // single step: rewrite to axis step (required to sort results of path) step = Step.get(((Context) step).info, SELF, Test.NOD); } else if (step instanceof Filter) { // rewrite to axis step (can be evaluated faster than filter expression) final Filter f = (Filter) step; if (f.root instanceof Context) step = Step.get(f.info, SELF, Test.NOD, f.preds); } stps.add(step); } // check if all steps are axis steps boolean axes = true; final Expr[] st = stps.finish(); for (final Expr step : st) axes &= step instanceof Step; // choose best implementation return axes ? iterative(rt, st) ? new IterPath(info, rt, st) : new CachedPath(info, rt, st) : new MixedPath(info, rt, st); }
/** * Returns an equivalent expression which accesses an index. If the expression cannot be * rewritten, the original expression is returned. * * <p>The following types of queries can be rewritten (in the examples, the equality comparison is * used, which will be rewritten to {@link ValueAccess} instances): * * <pre> * 1. A[text() = '...'] -> IA('...') * 2. A[. = '...'] -> IA('...', A) * 3. text()[. = '...'] -> IA('...') * 4. A[B = '...'] -> IA('...', B)/parent::A * 1. A[B/text() = '...'] -> IA('...')/parent::B/parent::A * 2. A[B/C = '...'] -> IA('...', C)/parent::B/parent::A * 7. A[@a = '...'] -> IA('...', @a)/parent::A * 8. @a[. = '...'] -> IA('...', @a)</pre> * * Queries of type 1, 3, 5 will not yield any results if the string to be compared is empty. * * @param qc query context * @param rt root value * @return original or new expression * @throws QueryException query exception */ private Expr index(final QueryContext qc, final Value rt) throws QueryException { // only rewrite paths with data reference final Data data = rt.data(); if (data == null) return this; // cache index access costs IndexInfo index = null; // cheapest predicate and step int iPred = 0, iStep = 0; // check if path can be converted to an index access final int sl = steps.length; for (int s = 0; s < sl; s++) { // only accept descendant steps without positional predicates final Step step = axisStep(s); if (step == null || !step.axis.down || step.has(Flag.FCS)) break; // check if path is iterable (i.e., will be duplicate-free) final boolean iter = pathNodes(data, s) != null; final IndexContext ictx = new IndexContext(data, iter); // choose cheapest index access final int pl = step.preds.length; for (int p = 0; p < pl; p++) { final IndexInfo ii = new IndexInfo(ictx, qc, step); if (!step.preds[p].indexAccessible(ii)) continue; if (ii.costs == 0) { // no results... qc.compInfo(OPTNOINDEX, this); return Empty.SEQ; } if (index == null || index.costs > ii.costs) { index = ii; iPred = p; iStep = s; } } } // skip rewriting if no index access is possible, or if it is too expensive if (index == null || index.costs > data.meta.size) return this; // rewrite for index access qc.compInfo(index.info); // replace expressions for index access final Step indexStep = index.step; // collect remaining predicates final int pl = indexStep.preds.length; final ExprList newPreds = new ExprList(pl - 1); for (int p = 0; p < pl; p++) { if (p != iPred) newPreds.add(indexStep.preds[p]); } // invert steps that occur before index step and add them as predicate final Test test = InvDocTest.get(rt); final ExprList invSteps = new ExprList(); if (test != Test.DOC || !data.meta.uptodate || predSteps(data, iStep)) { for (int s = iStep; s >= 0; s--) { final Axis ax = axisStep(s).axis.invert(); if (s == 0) { // add document test for collections and axes other than ancestors if (test != Test.DOC || ax != Axis.ANC && ax != Axis.ANCORSELF) invSteps.add(Step.get(info, ax, test)); } else { final Step prev = axisStep(s - 1); invSteps.add(Step.get(info, ax, prev.test, prev.preds)); } } } if (!invSteps.isEmpty()) newPreds.add(get(info, null, invSteps.finish())); // create resulting expression final ExprList resultSteps = new ExprList(); final Expr resultRoot; if (index.expr instanceof Path) { final Path p = (Path) index.expr; resultRoot = p.root; resultSteps.add(p.steps); } else { resultRoot = index.expr; } if (!newPreds.isEmpty()) { int ls = resultSteps.size() - 1; Step step; if (ls < 0 || !(resultSteps.get(ls) instanceof Step)) { // add at least one self axis step step = Step.get(info, Axis.SELF, Test.NOD); ls++; } else { step = (Step) resultSteps.get(ls); } // add remaining predicates to last step resultSteps.set(ls, step.addPreds(newPreds.finish())); } // add remaining steps for (int s = iStep + 1; s < sl; s++) resultSteps.add(steps[s]); return get(info, resultRoot, resultSteps.finish()); }
/** * 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; }