@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 Expr inline(final QueryContext qc, final VarScope scp, final Var var, final Expr ex) throws QueryException { boolean change = false; try { final Expr sub = expr.inline(qc, scp, var, ex); if (sub != null) { if (sub.isValue()) return optPre(sub, qc); expr = sub; change = true; } } catch (final QueryException qe) { if (!qe.isCatchable()) throw qe; for (final Catch c : catches) { if (c.matches(qe)) { // found a matching clause, inline variable and error message final Catch nw = c.inline(qc, scp, var, ex); return optPre((nw == null ? c : nw).asExpr(qe, qc, scp), qc); } } throw qe; } for (final Catch c : catches) change |= c.inline(qc, scp, var, ex) != null; return change ? this : null; }
@Override public boolean has(final Flag flag) { for (final Expr pred : preds) { if (flag == Flag.FCS && pred.seqType().mayBeNumber() || pred.has(flag)) 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; }
@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 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 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 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; }
/** * Ensures that none of the specified expressions performs an update. Otherwise, throws an * exception. * * @param exprs expressions (may be {@code null}, and may contain {@code null} references) * @throws QueryException query exception */ protected final void checkNoneUp(final Expr... exprs) throws QueryException { if (exprs == null) return; checkAllUp(exprs); for (final Expr expr : exprs) { if (expr != null && expr.has(Flag.UPD)) throw UPNOT_X.get(info, description()); } }
@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); }
/** * Checks if the predicates are successful for the specified item. * * @param it item to be checked * @param qc query context * @return result of check * @throws QueryException query exception */ protected final boolean preds(final Item it, final QueryContext qc) throws QueryException { if (preds.length == 0) return true; // set context value and position final Value cv = qc.value; try { if (qc.scoring) { double s = 0; for (final Expr p : preds) { qc.value = it; final Item i = p.test(qc, info); if (i == null) return false; s += i.score(); } it.score(Scoring.avg(s, preds.length)); } else { for (final Expr p : preds) { qc.value = it; if (p.test(qc, info) == null) return false; } } return true; } finally { qc.value = cv; } }
@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 public boolean accept(final ASTVisitor visitor) { for (final Expr e : preds) { visitor.enterFocus(); if (!e.accept(visitor)) return false; visitor.exitFocus(); } return root.accept(visitor); }
@Override public VarUsage count(final Var v) { final VarUsage inPreds = super.count(v), inRoot = root.count(v); if (inPreds == VarUsage.NEVER) return inRoot; final long sz = root.size(); return sz >= 0 && sz <= 1 || root.type().zeroOrOne() ? inRoot.plus(inPreds) : VarUsage.MORE_THAN_ONCE; }
@Override public Expr optimize(final QueryContext qc, final VarScope scp) throws QueryException { final Expr c = super.optimize(qc, scp); if (c != this) return c; final int es = exprs.length; final ExprList list = new ExprList(es); for (int i = 0; i < es; i++) { Expr e = exprs[i]; if (e instanceof CmpG) { // merge adjacent comparisons while (i + 1 < es && exprs[i + 1] instanceof CmpG) { final Expr tmp = ((CmpG) e).union((CmpG) exprs[i + 1], qc, scp); if (tmp != null) { e = tmp; i++; } else { break; } } } // expression will always return true if (e == Bln.TRUE) return optPre(Bln.TRUE, qc); // skip expression yielding false if (e != Bln.FALSE) list.add(e); } // all arguments return false if (list.isEmpty()) return optPre(Bln.FALSE, qc); if (es != list.size()) { qc.compInfo(OPTREWRITE_X, this); exprs = list.finish(); } compFlatten(qc); boolean not = true; for (final Expr expr : exprs) { if (!expr.isFunction(Function.NOT)) { not = false; break; } } if (not) { qc.compInfo(OPTREWRITE_X, this); final int el = exprs.length; final Expr[] inner = new Expr[el]; for (int e = 0; e < el; e++) inner[e] = ((Arr) exprs[e]).exprs[0]; final Expr ex = new And(info, inner).optimize(qc, scp); return Function.NOT.get(null, info, ex).optimize(qc, scp); } // return single expression if it yields a boolean return exprs.length == 1 ? compBln(exprs[0], info) : this; }
@Override public String toString() { final TokenBuilder tb = new TokenBuilder("map { "); boolean key = true; for (final Expr e : expr) { tb.add(key ? tb.size() > 6 ? ", " : "" : ":=").add(e.toString()); key ^= true; } return tb.add(" }").toString(); }
/** * Ensures that all specified expressions are vacuous or either updating or non-updating. * Otherwise, throws an exception. * * @param exprs expressions to be checked * @throws QueryException query exception */ void checkAllUp(final Expr... exprs) throws QueryException { // updating state: 0 = initial state, 1 = updating, -1 = non-updating int s = 0; for (final Expr expr : exprs) { expr.checkUp(); if (expr.isVacuous()) continue; final boolean u = expr.has(Flag.UPD); if (u && s == -1 || !u && s == 1) throw UPALL.get(info, description()); s = u ? 1 : -1; } }
/** * Prepares this expression for iterative evaluation. The expression can be iteratively evaluated * if no predicate or only the first is positional. * * @return result of check */ protected final boolean posIterator() { // check if first predicate is numeric if (preds.length == 1) { Expr p = preds[0]; if (p instanceof Int) p = Pos.get(((Int) p).itr(), info); pos = p instanceof Pos ? (Pos) p : null; last = p.isFunction(Function.LAST); preds[0] = p; } return pos != null || last; }
@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 Expr compile(final QueryContext qc, final VarScope scp) throws QueryException { ts = ts.compile(qc, scp); // static condition: return branch in question if (ts.isValue()) { final Value val = ts.value(qc); for (final TypeCase tc : cases) { if (tc.matches(val)) return optPre(tc.compile(qc, scp, (Value) ts).expr, qc); } } // compile branches for (final TypeCase tc : cases) tc.compile(qc, scp); return optimize(qc, scp); }
/** * 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; }
@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 final Expr optimize(final QueryContext ctx, final VarScope scp) throws QueryException { // invalidate current context value (will be overwritten by filter) final Value cv = ctx.value; try { // return empty root if (root.isEmpty()) return optPre(null, ctx); // convert filters without numeric predicates to axis paths if (root instanceof AxisPath && !super.has(Flag.FCS)) return ((AxisPath) root.copy(ctx, scp)).addPreds(ctx, scp, preds); // no predicates.. return root; otherwise, do some advanced compilations return preds.length == 0 ? root : opt(ctx); } finally { ctx.value = cv; } }
@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 public Expr inline(final QueryContext ctx, final VarScope scp, final Var v, final Expr e) throws QueryException { final boolean pr = super.inline(ctx, scp, v, e) != null; final Expr rt = root == null ? null : root.inline(ctx, scp, v, e); if (rt != null) root = rt; return pr || rt != null ? optimize(ctx, scp) : null; }
/** * 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; }
/** * Merges a single predicate with the root expression and returns the resulting expression, or * returns a self reference if the expression cannot be merged. This function is e.g. called by * {@link Where#optimize}. * * @param qc query context * @param scp variable scope * @param root root expression * @return expression * @throws QueryException query exception */ public Expr merge(final Expr root, final QueryContext qc, final VarScope scp) throws QueryException { // only one predicate can be rewritten; root expression must yield nodes if (preds.length != 1 || !(root.seqType().type instanceof NodeType)) return this; final Expr pred = preds[0]; // a[.] -> a if (pred instanceof Context) return root; if (!pred.seqType().mayBeNumber()) { // a[b] -> a/b if (pred instanceof Path) return Path.get(info, root, pred).optimize(qc, scp); if (pred instanceof CmpG) { final CmpG cmp = (CmpG) pred; final Expr expr1 = cmp.exprs[0], expr2 = cmp.exprs[1]; // only first operand can depend on context if (!expr2.has(Flag.CTX)) { Expr path = null; // a[. = 'x'] -> a = 'x' if (expr1 instanceof Context) path = root; // a[text() = 'x'] -> a/text() = 'x' if (expr1 instanceof Path) path = Path.get(info, root, expr1).optimize(qc, scp); if (path != null) return new CmpG(path, expr2, cmp.op, cmp.coll, cmp.sc, cmp.info); } } if (pred instanceof FTContains) { final FTContains cmp = (FTContains) pred; final FTExpr ftexpr = cmp.ftexpr; // only first operand can depend on context if (!ftexpr.has(Flag.CTX)) { final Expr expr = cmp.expr; Expr path = null; // a[. contains text 'x'] -> a contains text 'x' if (expr instanceof Context) path = root; // [text() contains text 'x'] -> a/text() contains text 'x' if (expr instanceof Path) path = Path.get(info, root, expr).optimize(qc, scp); if (path != null) return new FTContains(path, ftexpr, cmp.info); } } } return this; }
@Override public boolean indexAccessible(final IndexInfo ii) throws QueryException { int costs = 0; final ExprList el = new ExprList(exprs.length); for (final Expr expr : exprs) { // check if expression can be rewritten, and if access is not sequential if (!expr.indexAccessible(ii)) return false; // skip expressions without results if (ii.costs == 0) continue; costs += ii.costs; el.add(ii.expr); } // use summarized costs for estimation ii.costs = costs; // no expressions means no costs: expression will later be ignored ii.expr = el.size() == 1 ? el.get(0) : new Union(info, el.finish()); return true; }