@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; }
@Override public final Expr compile(final QueryContext qc, final VarScope scp) throws QueryException { if (root != null) root = root.compile(qc, scp); // no steps if (steps.length == 0) return root == null ? new Context(info) : root; final Value init = qc.value, cv = initial(qc); final boolean doc = cv != null && cv.type == NodeType.DOC; qc.value = cv; try { final int sl = steps.length; for (int s = 0; s < sl; s++) { Expr e = steps[s]; // axis step: if input is a document, its type is temporarily generalized final boolean as = e instanceof Step; if (as && s == 0 && doc) cv.type = NodeType.NOD; e = e.compile(qc, scp); if (e.isEmpty()) return optPre(qc); steps[s] = e; // no axis step: invalidate context value if (!as) qc.value = null; } } finally { if (doc) cv.type = NodeType.DOC; qc.value = init; } // optimize path return optimize(qc, scp); }
@Override public int exprSize() { int sz = 1; for (final Let lt : copies) sz += lt.exprSize(); for (final Expr e : expr) sz += e.exprSize(); return sz; }
@Override public NodeIter iter(final QueryContext ctx) throws QueryException { final Value v = checkCtx(ctx); if (!v.type.isNode()) NODESPATH.thrw(input, AxisStep.this, v.type); final AxisIter ai = axis.iter((ANode) v); final NodeCache nc = new NodeCache(); for (ANode n; (n = ai.next()) != null; ) if (test.eval(n)) nc.add(n.finish()); // evaluate predicates for (final Expr p : preds) { ctx.size = nc.size(); ctx.pos = 1; int c = 0; for (int n = 0; n < nc.size(); ++n) { ctx.value = nc.get(n); final Item i = p.test(ctx, input); if (i != null) { // assign score value nc.get(n).score(i.score()); nc.item[c++] = nc.get(n); } ctx.pos++; } nc.size(c); } return nc; }
@Override public final boolean has(final Flag flag) { // first step or root expression will be used as context if (flag == Flag.CTX) return root == null || root.has(flag); for (final Expr s : steps) if (s.has(flag)) return true; return root != null && root.has(flag); }
@Override public boolean accept(final ASTVisitor visitor) { if (!visitAll(visitor, specs)) return false; for (final Expr ng : preExpr) if (!ng.accept(visitor)) return false; for (final Var ng : post) if (!visitor.declared(ng)) return false; return true; }
@Override public int exprSize() { int sz = 1; if (occ != null) for (final Expr o : occ) sz += o.exprSize(); for (final Expr e : exprs) sz += e.exprSize(); return sz + query.exprSize(); }
@Override public int exprSize() { int sz = 0; for (final Expr e : preExpr) sz += e.exprSize(); for (final Expr e : specs) sz += e.exprSize(); return sz; }
@Override public void checkUp() throws QueryException { for (final Let c : copies) c.checkUp(); final Expr m = expr[0]; m.checkUp(); if (!m.isVacuous() && !m.has(Flag.UPD)) throw UPMODIFY.get(info); checkNoUp(expr[1]); }
@Override protected Expr opt(final QueryContext qc, final VarScope scp) { final Expr e = exprs[0]; final SeqType st = e.seqType(); if (!st.mayBeZero()) return e; seqType = SeqType.get(st.type, seqType.occ); return this; }
@Override public Expr optimize(final QueryContext ctx, final VarScope scp) throws QueryException { if (root instanceof Context) { ctx.compInfo(OPTREMCTX); root = null; } for (final Expr e : steps) { // check for empty steps if (e.isEmpty()) return optPre(null, ctx); } return this; }
@Override public Item item(final QueryContext qc, final InputInfo ii) throws QueryException { final QNm name = toQNm(exprs[0], qc, sc, false); final long arity = toLong(exprs[1], qc); if (arity < 0 || arity > Integer.MAX_VALUE) throw FUNCUNKNOWN_X.get(ii, name); try { final Expr lit = Functions.getLiteral(name, (int) arity, qc, sc, ii); return lit == null ? null : lit.item(qc, ii); } catch (final QueryException e) { // function not found (in most cases: XPST0017) return null; } }
/** * Returns the initial context value of a path or {@code null}. * * @param qc query context (may be @code null) * @return root */ private Value initial(final QueryContext qc) { // current context value final Value v = qc != null ? qc.value : null; // no root or context expression: return context if (root == null || root instanceof Context) return v; // root reference if (root instanceof Root) return v != null && v instanceof Item ? Root.root(v) : v; // root is value: return root if (root.isValue()) return (Value) root; // data reference final Data d = root.data(); if (d != null) return new DBNode(d, 0, Data.ELEM); // otherwise, return null return null; }
@Override public final Expr inline(final QueryContext ctx, final VarScope scp, final Var v, final Expr e) throws QueryException { final Value oldVal = ctx.value; try { ctx.value = root(ctx); final Expr rt = root == null ? null : root.inline(ctx, scp, v, e); if (rt != null) { setRoot(ctx, rt); ctx.value = oldVal; ctx.value = root(ctx); } boolean change = rt != null; for (int i = 0; i < steps.length; i++) { final Expr nw = steps[i].inline(ctx, scp, v, e); if (nw != null) { steps[i] = nw; change = true; } } return change ? optimize(ctx, scp) : null; } finally { ctx.value = oldVal; } }
@Override public void plan(final Serializer ser) throws IOException { ser.openElement(this); weight.plan(ser); expr[0].plan(ser); ser.closeElement(); }
/** * Checks if the path can be rewritten for iterative evaluation. * * @param root root expression; can be a {@code null} reference * @param steps path steps * @return result of check */ private static boolean iterative(final Expr root, final Expr... steps) { if (root == null || !root.iterable()) return false; final int sl = steps.length; for (int s = 0; s < sl; ++s) { switch (((Step) steps[s]).axis) { // reverse axes - don't iterate case ANC: case ANCORSELF: case PREC: case PRECSIBL: return false; // multiple, unsorted results - only iterate at last step, // or if last step uses attribute axis case DESC: case DESCORSELF: case FOLL: case FOLLSIBL: return s + 1 == sl || s + 2 == sl && ((Step) steps[s + 1]).axis == Axis.ATTR; // allow iteration for CHILD, ATTR, PARENT and SELF axes default: } } return true; }
/** * 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; }
@Override public FTWords compile(final QueryContext qc, final VarScope scp) throws QueryException { if (occ != null) { final int ol = occ.length; for (int o = 0; o < ol; ++o) occ[o] = occ[o].compile(qc, scp); } // compile only once if (tokens == null) { query = query.compile(qc, scp); if (query.isValue()) tokens = tokens(qc); // choose fast evaluation for default settings fast = mode == FTMode.ANY && tokens != null && occ == null; if (ftt == null) ftt = new FTTokenizer(this, qc); } return this; }
@Override protected final Expr compPath(final QueryContext ctx) throws QueryException { for (final Expr s : steps) checkUp(s, ctx); // merge two axis paths if (root instanceof AxisPath) { Expr[] st = ((AxisPath) root).steps; root = ((AxisPath) root).root; for (final Expr s : steps) st = Array.add(st, s); steps = st; // refresh root context ctx.compInfo(OPTMERGE); ctx.value = root(ctx); } final AxisStep s = voidStep(steps); if (s != null) COMPSELF.thrw(input, s); for (int i = 0; i != steps.length; ++i) { final Expr e = steps[i].comp(ctx); if (!(e instanceof AxisStep)) return e; steps[i] = e; } optSteps(ctx); // retrieve data reference final Data data = ctx.data(); if (data != null && ctx.value.type == NodeType.DOC) { // check index access Expr e = index(ctx, data); // check children path rewriting if (e == this) e = children(ctx, data); // return optimized expression if (e != this) return e.comp(ctx); } // analyze if result set can be cached - no predicates/variables... cache = root != null && !uses(Use.VAR); // if applicable, use iterative evaluation final Path path = finish(ctx); // heuristics: wrap with filter expression if only one result is expected return size() != 1 ? path : new Filter(input, this, Pos.get(1, size(), input)).comp2(ctx); }
@Override public FTExpr inline(final QueryContext qc, final VarScope scp, final Var var, final Expr ex) throws QueryException { boolean change = occ != null && inlineAll(qc, scp, occ, var, ex); final Expr q = query.inline(qc, scp, var, ex); if (q != null) { query = q; change = true; } return change ? optimize(qc, scp) : null; }
/** * Returns the root of the current context or {@code null}. * * @param ctx query context * @return root */ final Value root(final QueryContext ctx) { final Value v = ctx != null ? ctx.value : null; // no root specified: return context, if it does not reference a document // as e.g. happens in //a(b|c) if (root == null) return v == null || v.type != NodeType.DOC ? v : null; // root is value: return root if (root.isValue()) return (Value) root; // no root reference, no context: return null if (!(root instanceof Root) || v == null) return null; // return context sequence or root of current context return v.size() == 1 ? Root.root(v) : v; }
@Override public final Expr compile(final QueryContext ctx, final VarScope scp) throws QueryException { if (root != null) setRoot(ctx, root.compile(ctx, scp)); final Value v = ctx.value; try { ctx.value = root(ctx); return compilePath(ctx, scp); } finally { ctx.value = v; } }
@Override public boolean accept(final ASTVisitor visitor) { if (root == null) { if (!visitor.lock(DBLocking.CTX)) return false; } else if (!root.accept(visitor)) { return false; } visitor.enterFocus(); if (!visitAll(visitor, steps)) return false; visitor.exitFocus(); return true; }
@Override public Expr optimize(final QueryContext ctx, final VarScope scp) throws QueryException { final int ar = expr.length - 1; final Expr f = expr[ar]; final Type t = f.type().type; if (t instanceof FuncType) { final FuncType ft = (FuncType) t; if (ft.args != null && ft.args.length != ar) throw INVARITY.get(info, f, ar); if (ft.ret != null) type = ft.ret; } if (f instanceof XQFunctionExpr) { // maps can only contain fully evaluated Values, so this is safe if (allAreValues() && f instanceof Map) return optPre(value(ctx), ctx); // try to inline the function if (!(f instanceof FuncItem && comesFrom((FuncItem) f)) && !updating) { final Expr[] args = Arrays.copyOf(expr, expr.length - 1); final Expr inl = ((XQFunctionExpr) f).inlineExpr(args, ctx, scp, info); if (inl != null) return inl; } } return this; }
@Override public Data data() { if (root != null) { // data reference final Data data = root.data(); if (data != null) { final int sl = steps.length; for (int s = 0; s < sl; s++) { if (axisStep(s) == null) return null; } return data; } } return null; }
@Override public FTExpr copy(final QueryContext qc, final VarScope scp, final IntObjMap<Var> vs) { final FTWords ftw = new FTWords( info, query.copy(qc, scp, vs), mode, occ == null ? null : Arr.copyAll(qc, scp, vs, occ)); if (ftt != null) ftw.ftt = ftt.copy(ftw); ftw.tokens = tokens; ftw.data = data; ftw.first = first; ftw.pos = pos; ftw.fast = fast; return ftw; }
@Override public final Expr inline(final QueryContext qc, final VarScope scp, final Var var, final Expr ex) throws QueryException { boolean changed = false; if (root != null) { final Expr rt = root.inline(qc, scp, var, ex); if (rt != null) { root = rt; changed = true; } } final int sl = steps.length; for (int s = 0; s < sl; s++) { final Expr nw = steps[s].inline(qc, scp, var, ex); if (nw != null) { steps[s] = nw; changed = true; } } return changed ? optimize(qc, scp) : null; }
@Override public GroupBy compile(final QueryContext qc, final VarScope sc) throws QueryException { for (final Expr e : preExpr) e.compile(qc, sc); for (final Spec b : specs) b.compile(qc, sc); return optimize(qc, sc); }
@Override public final int exprSize() { int sz = 1; for (final Expr e : steps) sz += e.exprSize(); return root == null ? sz : sz + root.exprSize(); }
@Override public VarUsage count(final Var var) { final VarUsage inRoot = root == null ? VarUsage.NEVER : root.count(var); return VarUsage.sum(var, steps) == VarUsage.NEVER ? inRoot : VarUsage.MORE_THAN_ONCE; }