@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; }
/** * 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()); } }
/** * 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 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 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 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; }
/** * 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 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; } }
/** * 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; }
/** * 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()); }
/** * Returns a boolean equivalent for the specified expression. If the specified expression yields a * boolean value anyway, it will be returned as is. Otherwise, it will be wrapped into a boolean * function. * * @param ex expression to be rewritten * @param info input info * @param sc static context * @return expression */ protected static Expr compBln(final Expr ex, final InputInfo info, final StaticContext sc) { return ex.seqType().eq(SeqType.BLN) ? ex : Function.BOOLEAN.get(sc, info, ex); }
/** * Checks if the specified expression yields a QName. Returns the item or throws an exception. * * @param ex expression to be evaluated * @param qc query context * @param empty allow empty result * @return QNm item * @throws QueryException query exception */ protected final QNm toQNm(final Expr ex, final QueryContext qc, final boolean empty) throws QueryException { return toQNm(ex.atomItem(qc, info), empty); }
/** * Checks if the specified expression yields an item of the specified atomic type. Returns the * item or throws an exception. * * @param ex expression to be evaluated * @param qc query context * @param type type to be checked * @return item * @throws QueryException query exception */ protected Item checkAtomic(final Expr ex, final QueryContext qc, final Type type) throws QueryException { return checkType(ex.atomItem(qc, info), type); }
/** * Checks if the specified expression yields a binary item. Returns the binary item or throws an * exception. * * @param ex expression to be evaluated * @param qc query context * @return binary item * @throws QueryException query exception */ protected final Bin toBin(final Expr ex, final QueryContext qc) throws QueryException { return toBin(ex.atomItem(qc, info)); }
/** * Checks if the specified expression yields a string or binary item. * * @param ex expression to be evaluated * @param qc query context * @return byte array * @throws QueryException query exception */ protected final byte[] toBytes(final Expr ex, final QueryContext qc) throws QueryException { return toBytes(ex.atomItem(qc, info)); }
/** * Checks if the evaluated expression yields a non-empty item. Returns the atomized item or throws * an exception. * * @param ex expression to be evaluated * @param qc query context * @return atomized item * @throws QueryException query exception */ protected final Item toAtomItem(final Expr ex, final QueryContext qc) throws QueryException { return checkNoEmpty(ex.atomItem(qc, info)); }
/** * Checks if the specified expression yields a double. Returns the double or throws an exception. * * @param ex expression to be evaluated * @param qc query context * @return double * @throws QueryException query exception */ protected final double toDouble(final Expr ex, final QueryContext qc) throws QueryException { return toDouble(ex.atomItem(qc, info)); }
/** * Checks if the specified expression yields a node or {@code null}. Returns the node, {@code * null}, or throws an exception. * * @param ex expression to be evaluated * @param qc query context * @return node or {@code null} * @throws QueryException query exception */ protected final ANode toEmptyNode(final Expr ex, final QueryContext qc) throws QueryException { final Item it = ex.item(qc, info); return it == null ? null : toNode(it); }
/** * Checks if the specified expression yields a non-empty item. Returns the item or throws an * exception. * * @param ex expression to be evaluated * @param qc query context * @param type expected type * @return item * @throws QueryException query exception */ private Item toItem(final Expr ex, final QueryContext qc, final Type type) throws QueryException { return checkNoEmpty(ex.item(qc, info), type); }
/** * Checks if the specified expression yields a node. Returns the boolean or throws an exception. * * @param ex expression to be evaluated * @param qc query context * @return node * @throws QueryException query exception */ protected final ANode toNode(final Expr ex, final QueryContext qc) throws QueryException { return toNode(checkNoEmpty(ex.item(qc, info), NodeType.NOD)); }
/** * Checks if the specified expression yields a float. Returns the float or throws an exception. * * @param ex expression to be evaluated * @param qc query context * @return float * @throws QueryException query exception */ protected final float toFloat(final Expr ex, final QueryContext qc) throws QueryException { final Item it = ex.atomItem(qc, info); if (checkNoEmpty(it, AtomType.FLT).type.isNumberOrUntyped()) return it.flt(info); throw numberError(this, it); }
/** * Checks if the specified expression yields a string. Returns a value as token or throws an * exception. * * @param ex expression to be evaluated * @param qc query context * @return token * @throws QueryException query exception */ protected final byte[] toToken(final Expr ex, final QueryContext qc) throws QueryException { final Item it = ex.atomItem(qc, info); if (it == null) throw EMPTYFOUND_X.get(info, AtomType.STR); return toToken(it); }
@Override public void plan(final FElem plan) { for (final Expr p : preds) p.plan(plan); }
/** * Checks if the specified expression yields an element. Returns the element or throws an * exception. * * @param ex expression to be evaluated * @param qc query context * @return binary item * @throws QueryException query exception */ protected final ANode toElem(final Expr ex, final QueryContext qc) throws QueryException { return (ANode) checkType(ex.item(qc, info), NodeType.ELM); }
/** * Checks if the specified expression yields a number or {@code null}. Returns the number, {@code * null}, or throws an exception. * * @param ex expression to be evaluated * @param qc query context * @return double * @throws QueryException query exception */ protected final ANum toNumber(final Expr ex, final QueryContext qc) throws QueryException { final Item it = ex.atomItem(qc, info); return it == null ? null : toNumber(it); }
/** * Checks if the specified expression yields a string or an empty sequence. Returns a value as * token or throws an exception. * * @param ex expression to be evaluated * @param qc query context * @return token (empty string if result is an empty sequence) * @throws QueryException query exception */ protected final byte[] toEmptyToken(final Expr ex, final QueryContext qc) throws QueryException { final Item it = ex.atomItem(qc, info); return it == null ? EMPTY : toToken(it); }