@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 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; }
/** * 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 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 Expr comp(final QueryContext ctx) throws QueryException { ts = checkUp(ts, ctx).comp(ctx); final Expr[] tmp = new Expr[cases.length]; for (int i = 0; i < cases.length; ++i) tmp[i] = cases[i].expr; checkUp(ctx, tmp); // static condition: return branch in question if (ts.isValue()) { for (final TypeCase c : cases) { if (c.var.type == null || c.var.type.instance(ts.value(ctx))) return optPre(c.comp(ctx, (Value) ts).expr, ctx); } } // compile branches for (final TypeCase c : cases) c.comp(ctx); // return result if all branches are equal (e.g., empty) boolean eq = true; for (int i = 1; i < cases.length; ++i) { eq &= cases[i - 1].expr.sameAs(cases[i].expr); } if (eq) return optPre(null, ctx); // evaluate return type type = cases[0].type(); for (int c = 1; c < cases.length; ++c) { type = type.intersect(cases[c].type()); } return this; }
@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 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(); }
/** * 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 boolean uses(final Use u) { for (final Expr p : pred) { if (u == Use.POS && p.type().mayBeNum() || p.uses(u)) return true; } return false; }
@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 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 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 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; }
/** * 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; }
/** * 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; } }
/** * Tests if the specified expressions are updating or vacuous. * * @param ctx query context * @param expr expression array * @throws QueryException query exception */ public void checkUp(final QueryContext ctx, final Expr... expr) throws QueryException { if (!ctx.updating) return; int s = 0; for (final Expr e : expr) { if (e.vacuous()) continue; final boolean u = e.uses(Use.UPD); if (u && s == 2 || !u && s == 1) UPNOT.thrw(input, desc()); s = u ? 1 : 2; } }
/** * Checks if the predicates are successful for the specified item. * * @param it item to be checked * @param ctx query context * @return result of check * @throws QueryException query exception */ public boolean preds(final Item it, final QueryContext ctx) throws QueryException { // set context item and position ctx.value = it; for (final Expr p : pred) { final Item i = p.test(ctx, input); if (i == null) return false; // item accepted.. adopt last scoring value it.score(i.score()); } return true; }
@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; }
/** * 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 void plan(final Serializer ser) throws IOException { ser.openElement(this); for (final TypeCase c : cases) c.plan(ser); ts.plan(ser); ser.closeElement(); }
@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 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; }
/** * 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; }
@Override public Expr optimize(final QueryContext qc, final VarScope scp) throws QueryException { // number of predicates may change in loop for (int p = 0; p < preds.length; p++) { final Expr pred = preds[p]; if (pred instanceof CmpG || pred instanceof CmpV) { final Cmp cmp = (Cmp) pred; if (cmp.exprs[0].isFunction(Function.POSITION)) { final Expr e2 = cmp.exprs[1]; final SeqType st2 = e2.seqType(); // position() = last() -> last() // position() = $n (numeric) -> $n if (e2.isFunction(Function.LAST) || st2.one() && st2.type.isNumber()) { if (cmp instanceof CmpG && ((CmpG) cmp).op == OpG.EQ || cmp instanceof CmpV && ((CmpV) cmp).op == OpV.EQ) { qc.compInfo(OPTWRITE, pred); preds[p] = e2; } } } } else if (pred instanceof And) { if (!pred.has(Flag.FCS)) { // replace AND expression with predicates (don't swap position tests) qc.compInfo(OPTPRED, pred); final Expr[] and = ((Arr) pred).exprs; final int m = and.length - 1; final ExprList el = new ExprList(preds.length + m); for (final Expr e : Arrays.asList(preds).subList(0, p)) el.add(e); for (final Expr a : and) { // wrap test with boolean() if the result is numeric el.add(Function.BOOLEAN.get(null, info, a).optimizeEbv(qc, scp)); } for (final Expr e : Arrays.asList(preds).subList(p + 1, preds.length)) el.add(e); preds = el.finish(); } } else if (pred instanceof ANum) { final ANum it = (ANum) pred; final long i = it.itr(); if (i == it.dbl()) { preds[p] = Pos.get(i, info); } else { qc.compInfo(OPTREMOVE, this, pred); return Empty.SEQ; } } else if (pred.isValue()) { if (pred.ebv(qc, info).bool(info)) { qc.compInfo(OPTREMOVE, this, pred); preds = Array.delete(preds, p--); } else { // handle statically known predicates qc.compInfo(OPTREMOVE, this, pred); return Empty.SEQ; } } } return this; }
@Override public Item item(final QueryContext qc, final InputInfo ii) throws QueryException { // compute scoring if (qc.scoring) { double s = 0; boolean f = false; for (final Expr expr : exprs) { final Item it = expr.ebv(qc, info); f |= it.bool(ii); s += it.score(); } return Bln.get(f, Scoring.avg(s, exprs.length)); } // standard evaluation for (final Expr expr : exprs) { if (expr.ebv(qc, info).bool(ii)) return Bln.TRUE; } return Bln.FALSE; }