@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); }
/** * 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 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 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; }
/** * 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; } }
/** * 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; }
/** * Ensures that the specified expression performs no updates. Otherwise, throws an exception. * * @param expr expression (may be {@code null}) * @throws QueryException query exception */ protected void checkNoUp(final Expr expr) throws QueryException { if (expr == null) return; expr.checkUp(); if (expr.has(Flag.UPD)) throw UPNOT_X.get(info, description()); }