@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 Value value(final QueryContext ctx) throws QueryException { final int o = (int) ctx.resources.output.size(); final Updates updates = ctx.resources.updates(); final ContextModifier tmp = updates.mod; final TransformModifier pu = new TransformModifier(); updates.mod = pu; try { for (final Let fo : copies) { final Iter ir = ctx.iter(fo.expr); Item i = ir.next(); if (!(i instanceof ANode) || ir.next() != null) throw UPCOPYMULT.get(fo.info, fo.var.name); // copy node to main memory data instance i = ((ANode) i).dbCopy(ctx.context.options); // add resulting node to variable ctx.set(fo.var, i, info); pu.addData(i.data()); } final Value v = ctx.value(expr[0]); if (!v.isEmpty()) throw BASEX_MOD.get(info); updates.prepare(); updates.apply(); return ctx.value(expr[1]); } finally { ctx.resources.output.size(o); updates.mod = tmp; } }
@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); }
/** * Converts descendant to child steps. * * @param qc query context * @param rt root value * @return original or new expression */ private Expr children(final QueryContext qc, final Value rt) { // skip if index does not exist or is out-dated, or if several namespaces occur in the input final Data data = rt.data(); if (data == null || !data.meta.uptodate || data.nspaces.globalNS() == null) return this; Path path = this; final int sl = steps.length; for (int s = 0; s < sl; s++) { // don't allow predicates in preceding location steps final Step prev = s > 0 ? axisStep(s - 1) : null; if (prev != null && prev.preds.length != 0) break; // ignore axes other than descendant, or numeric predicates final Step curr = axisStep(s); if (curr == null || curr.axis != DESC || curr.has(Flag.FCS)) continue; // check if child steps can be retrieved for current step ArrayList<PathNode> nodes = pathNodes(data, s); if (nodes == null) continue; // cache child steps final ArrayList<QNm> qnm = new ArrayList<>(); while (nodes.get(0).parent != null) { QNm nm = new QNm(data.elemNames.key(nodes.get(0).name)); // skip children with prefixes if (nm.hasPrefix()) return this; for (final PathNode p : nodes) { if (nodes.get(0).name != p.name) nm = null; } qnm.add(nm); nodes = PathSummary.parent(nodes); } qc.compInfo(OPTCHILD, steps[s]); // build new steps int ts = qnm.size(); final Expr[] stps = new Expr[ts + sl - s - 1]; for (int t = 0; t < ts; t++) { final Expr[] preds = t == ts - 1 ? ((Preds) steps[s]).preds : new Expr[0]; final QNm nm = qnm.get(ts - t - 1); final NameTest nt = nm == null ? new NameTest(false) : new NameTest(nm, Kind.NAME, false, null); stps[t] = Step.get(info, CHILD, nt, preds); } while (++s < sl) stps[ts++] = steps[s]; path = get(info, root, stps); break; } // check if all steps yield results; if not, return empty sequence final ArrayList<PathNode> nodes = pathNodes(qc); if (nodes != null && nodes.isEmpty()) { qc.compInfo(OPTPATH, path); return Empty.SEQ; } return path; }
@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 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; } }
/** * Performs the test-uris function. * * @param ctx query context * @return resulting value * @throws QueryException query exception */ private Item testUris(final QueryContext ctx) throws QueryException { checkCreate(ctx); final ArrayList<IO> inputs = new ArrayList<>(); final Iter ir = ctx.iter(expr[0]); for (Item it; (it = ir.next()) != null; ) inputs.add(checkPath(it, ctx)); return new Suite(ctx, info).test(inputs); }
@Override Value[] evalArgs(final QueryContext ctx) throws QueryException { final int al = expr.length - 1; final Value[] args = new Value[al]; for (int a = 0; a < al; ++a) args[a] = ctx.value(expr[a]); return args; }
/** * Returns the specified function literal. * * @param name function name * @param arity number of arguments * @param dyn dynamic invocation flag * @param ctx query context * @param ii input info * @return literal function expression * @throws QueryException query exception */ public static FItem get( final QNm name, final long arity, final boolean dyn, final QueryContext ctx, final InputInfo ii) throws QueryException { final Expr[] args = new Expr[(int) arity]; final Var[] vars = new Var[args.length]; for (int i = 0; i < args.length; i++) { vars[i] = ctx.uniqueVar(ii, null); args[i] = new VarRef(ii, vars[i]); } final TypedFunc f = get(name, args, dyn, ctx, ii); if (f == null) { if (!dyn) FUNCUNKNOWN.thrw(ii, name + "#" + arity); return null; } // compile the function if it hasn't been done statically if (dyn && f.fun instanceof UserFuncCall) { final UserFunc usf = ((UserFuncCall) f.fun).func(); if (usf != null && usf.declared) usf.compile(ctx); } final FuncType ft = f.type; return new FuncItem(name, vars, f.fun, ft, false); }
/** * Optimizes descendant-or-self steps and static types. * * @param ctx query context */ void optSteps(final QueryContext ctx) { boolean opt = false; Expr[] st = steps; for (int l = 1; l < st.length; ++l) { if (!(st[l - 1] instanceof Step && st[l] instanceof Step)) continue; final Step prev = (Step) st[l - 1]; final Step curr = (Step) st[l]; if (!prev.simple(DESCORSELF, false)) continue; if (curr.axis == CHILD && !curr.has(Flag.FCS)) { // descendant-or-self::node()/child::X -> descendant::X final int sl = st.length; final Expr[] tmp = new Expr[sl - 1]; System.arraycopy(st, 0, tmp, 0, l - 1); System.arraycopy(st, l, tmp, l - 1, sl - l); st = tmp; curr.axis = DESC; opt = true; } else if (curr.axis == ATTR && !curr.has(Flag.FCS)) { // descendant-or-self::node()/@X -> descendant-or-self::*/@X prev.test = new NameTest(false); opt = true; } } if (opt) ctx.compInfo(OPTDESC); // set atomic type for single attribute steps to speedup predicate tests if (root == null && st.length == 1 && st[0] instanceof Step) { final Step curr = (Step) st[0]; if (curr.axis == ATTR && curr.test.mode == Mode.STD) curr.type = SeqType.NOD_ZO; } steps = st; }
/** * Sets a new root expression and eliminates a superfluous context item. * * @param ctx query context * @param rt root expression */ private void setRoot(final QueryContext ctx, final Expr rt) { root = rt; if (root instanceof Context) { ctx.compInfo(OPTREMCTX); root = null; } }
/** * Performs the assert-equals function. * * @param ctx query context * @return resulting value * @throws QueryException query exception */ private Item assertEquals(final QueryContext ctx) throws QueryException { final byte[] str = expr.length < 3 ? null : checkStr(expr[2], ctx); final Iter iter1 = ctx.iter(expr[0]), iter2 = ctx.iter(expr[1]); final Compare comp = new Compare(info); Item it1, it2; int c = 1; while (true) { it1 = iter1.next(); it2 = iter2.next(); final boolean empty1 = it1 == null, empty2 = it2 == null; if (empty1 && empty2) return null; if (empty1 || empty2 || !comp.deep(it1.iter(), it2.iter())) break; c++; } if (str != null) throw UNIT_MESSAGE.get(info, str); throw new UnitException(info, UNIT_ASSERT_EQUALS, it1, it2, c); }
/** * Returns a serializer for the given output stream. Optional output declarations within the query * will be included in the serializer instance. * * @param os output stream * @return serializer instance * @throws IOException query exception * @throws QueryException query exception */ public Serializer getSerializer(final OutputStream os) throws IOException, QueryException { compile(); try { return Serializer.get(os, qc.serParams()).sc(sc); } catch (final QueryIOException ex) { throw ex.getCause(); } }
/** * Parses the query. * * @throws QueryException query exception */ public void parse() throws QueryException { if (parsed) return; try { qc.parseMain(query, null, sc); } finally { parsed = true; updating = qc.updating; } }
/** * Returns all tokens of the query. * * @param qc query context * @return token list * @throws QueryException query exception */ private TokenList tokens(final QueryContext qc) throws QueryException { final TokenList tl = new TokenList(); final Iter ir = qc.iter(query); for (byte[] qu; (qu = nextToken(ir)) != null; ) { // skip empty tokens if not all results are needed if (qu.length != 0 || mode == FTMode.ALL || mode == FTMode.ALL_WORDS) tl.add(qu); } return tl; }
@Override public Iter iter(final QueryContext qc) throws QueryException { final Value seq = qc.value(ts); for (final TypeCase tc : cases) { final Iter iter = tc.iter(qc, seq); if (iter != null) return iter; } // will never happen throw Util.notExpected(); }
@Override public Value value(final QueryContext qc) throws QueryException { // don't catch errors from error handlers try { return qc.value(expr); } catch (final QueryException ex) { if (!ex.isCatchable()) throw ex; for (final Catch c : catches) if (c.matches(ex)) return c.value(qc, ex); throw ex; } }
@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 final Expr compile(final QueryContext ctx, final VarScope scp) throws QueryException { // invalidate current context value (will be overwritten by filter) final Value cv = ctx.value; try { root = root.compile(ctx, scp); // 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).compile(ctx, scp); // optimize filter expressions ctx.value = null; final Expr e = super.compile(ctx, scp); if (e != this) return e; // no predicates.. return root; otherwise, do some advanced compilations return preds.length == 0 ? root : opt(ctx); } finally { ctx.value = cv; } }
/** * Performs the test function. * * @param ctx query context * @return resulting value * @throws QueryException query exception */ private Item test(final QueryContext ctx) throws QueryException { final Unit unit = new Unit(ctx, info); if (expr.length == 0) return unit.test(sc); final ArrayList<StaticFunc> funcs = new ArrayList<>(); final Iter ir = ctx.iter(expr[0]); for (Item it; (it = ir.next()) != null; ) { final FItem fi = checkFunc(it, ctx); if (fi.funcName() != null) { final StaticFunc sf = ctx.funcs.get(fi.funcName(), fi.arity(), null, true); if (sf != null) funcs.add(sf); } } return unit.test(sc, funcs); }
/** * 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; }
/** * Returns an instance of a with the specified name and number of arguments, or {@code null}. * * @param name name of the function * @param args optional arguments * @param dyn compile-/run-time flag * @param ctx query context * @param ii input info * @return function instance * @throws QueryException query exception */ public static TypedFunc get( final QNm name, final Expr[] args, final boolean dyn, final QueryContext ctx, final InputInfo ii) throws QueryException { // get namespace and local name // parse data type constructors if (eq(name.uri(), XSURI)) { final byte[] ln = name.local(); final AtomType type = AtomType.find(name, false); if (type == null) { final Levenshtein ls = new Levenshtein(); for (final AtomType t : AtomType.values()) { if (t.par != null && t != AtomType.NOT && t != AtomType.AAT && t != AtomType.BIN && ls.similar(lc(ln), lc(t.string()), 0)) FUNSIMILAR.thrw(ii, name.string(), t.string()); } } // no constructor function found, or abstract type specified if (type == null || type == AtomType.NOT || type == AtomType.AAT) { FUNCUNKNOWN.thrw(ii, name.string()); } if (args.length != 1) FUNCTYPE.thrw(ii, name.string()); final SeqType to = SeqType.get(type, Occ.ZERO_ONE); return TypedFunc.constr(new Cast(ii, args[0], to), to); } // pre-defined functions final StandardFunc fun = Functions.get().get(name, args, ii); if (fun != null) { if (!ctx.sc.xquery3 && fun.xquery3()) FEATURE30.thrw(ii); for (final Function f : Function.UPDATING) { if (fun.sig == f) { ctx.updating(true); break; } } return new TypedFunc(fun, fun.sig.type(args.length)); } // user-defined function final TypedFunc tf = ctx.funcs.get(name, args, ii); if (tf != null) return tf; // Java function (only allowed with administrator permissions) final JavaMapping jf = JavaMapping.get(name, args, ctx, ii); if (jf != null) return TypedFunc.java(jf); // add user-defined function that has not been declared yet if (!dyn && FuncType.find(name) == null) return ctx.funcs.add(name, args, ii, ctx); // no function found return null; }
@Override public void close() { qc.close(); }
/** * Returns an equivalent expression which accesses an index. If the expression cannot be * rewritten, the original expression is returned. * * <p>The following types of queries can be rewritten (in the examples, the equality comparison is * used, which will be rewritten to {@link ValueAccess} instances): * * <pre> * 1. A[text() = '...'] -> IA('...') * 2. A[. = '...'] -> IA('...', A) * 3. text()[. = '...'] -> IA('...') * 4. A[B = '...'] -> IA('...', B)/parent::A * 1. A[B/text() = '...'] -> IA('...')/parent::B/parent::A * 2. A[B/C = '...'] -> IA('...', C)/parent::B/parent::A * 7. A[@a = '...'] -> IA('...', @a)/parent::A * 8. @a[. = '...'] -> IA('...', @a)</pre> * * Queries of type 1, 3, 5 will not yield any results if the string to be compared is empty. * * @param qc query context * @param rt root value * @return original or new expression * @throws QueryException query exception */ private Expr index(final QueryContext qc, final Value rt) throws QueryException { // only rewrite paths with data reference final Data data = rt.data(); if (data == null) return this; // cache index access costs IndexInfo index = null; // cheapest predicate and step int iPred = 0, iStep = 0; // check if path can be converted to an index access final int sl = steps.length; for (int s = 0; s < sl; s++) { // only accept descendant steps without positional predicates final Step step = axisStep(s); if (step == null || !step.axis.down || step.has(Flag.FCS)) break; // check if path is iterable (i.e., will be duplicate-free) final boolean iter = pathNodes(data, s) != null; final IndexContext ictx = new IndexContext(data, iter); // choose cheapest index access final int pl = step.preds.length; for (int p = 0; p < pl; p++) { final IndexInfo ii = new IndexInfo(ictx, qc, step); if (!step.preds[p].indexAccessible(ii)) continue; if (ii.costs == 0) { // no results... qc.compInfo(OPTNOINDEX, this); return Empty.SEQ; } if (index == null || index.costs > ii.costs) { index = ii; iPred = p; iStep = s; } } } // skip rewriting if no index access is possible, or if it is too expensive if (index == null || index.costs > data.meta.size) return this; // rewrite for index access qc.compInfo(index.info); // replace expressions for index access final Step indexStep = index.step; // collect remaining predicates final int pl = indexStep.preds.length; final ExprList newPreds = new ExprList(pl - 1); for (int p = 0; p < pl; p++) { if (p != iPred) newPreds.add(indexStep.preds[p]); } // invert steps that occur before index step and add them as predicate final Test test = InvDocTest.get(rt); final ExprList invSteps = new ExprList(); if (test != Test.DOC || !data.meta.uptodate || predSteps(data, iStep)) { for (int s = iStep; s >= 0; s--) { final Axis ax = axisStep(s).axis.invert(); if (s == 0) { // add document test for collections and axes other than ancestors if (test != Test.DOC || ax != Axis.ANC && ax != Axis.ANCORSELF) invSteps.add(Step.get(info, ax, test)); } else { final Step prev = axisStep(s - 1); invSteps.add(Step.get(info, ax, prev.test, prev.preds)); } } } if (!invSteps.isEmpty()) newPreds.add(get(info, null, invSteps.finish())); // create resulting expression final ExprList resultSteps = new ExprList(); final Expr resultRoot; if (index.expr instanceof Path) { final Path p = (Path) index.expr; resultRoot = p.root; resultSteps.add(p.steps); } else { resultRoot = index.expr; } if (!newPreds.isEmpty()) { int ls = resultSteps.size() - 1; Step step; if (ls < 0 || !(resultSteps.get(ls) instanceof Step)) { // add at least one self axis step step = Step.get(info, Axis.SELF, Test.NOD); ls++; } else { step = (Step) resultSteps.get(ls); } // add remaining predicates to last step resultSteps.set(ls, step.addPreds(newPreds.finish())); } // add remaining steps for (int s = iStep + 1; s < sl; s++) resultSteps.add(steps[s]); return get(info, resultRoot, resultSteps.finish()); }
@Override public void databases(final LockResult lr) { qc.databases(lr); }
/** * Returns the number of performed updates after query execution, or {@code 0}. * * @return number of updates */ public int updates() { return updating ? qc.updates().size() : 0; }
/** * Returns query information. * * @return query information */ public String info() { return qc.info(); }
/** * Returns a tree representation of the query plan. * * @return root node */ public FDoc plan() { return new FDoc().add(qc.plan()); }
/** * Compiles the query. * * @throws QueryException query exception */ public void compile() throws QueryException { parse(); qc.compile(); }
/** * Returns a memory-efficient result iterator. In most cases, the query will only be fully * evaluated if all items of this iterator are requested. * * @return result iterator * @throws QueryException query exception */ public Iter iter() throws QueryException { parse(); return qc.iter(); }