@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 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 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 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 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; }
/** * 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; } }
/** * 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; }
@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 FTExpr inline(final QueryContext qc, final VarScope scp, final Var var, final Expr ex) throws QueryException { boolean change = occ != null && inlineAll(qc, scp, occ, var, ex); final Expr q = query.inline(qc, scp, var, ex); if (q != null) { query = q; change = true; } return change ? optimize(qc, scp) : null; }
@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; } }
/** * Returns the root of the current context or {@code null}. * * @param ctx query context * @return root */ final Value root(final QueryContext ctx) { final Value v = ctx != null ? ctx.value : null; // no root specified: return context, if it does not reference a document // as e.g. happens in //a(b|c) if (root == null) return v == null || v.type != NodeType.DOC ? v : null; // root is value: return root if (root.isValue()) return (Value) root; // no root reference, no context: return null if (!(root instanceof Root) || v == null) return null; // return context sequence or root of current context return v.size() == 1 ? Root.root(v) : v; }
@Override public boolean accept(final ASTVisitor visitor) { if (root == null) { if (!visitor.lock(DBLocking.CTX)) return false; } else if (!root.accept(visitor)) { return false; } visitor.enterFocus(); if (!visitAll(visitor, steps)) return false; visitor.exitFocus(); return true; }
@Override public Data data() { if (root != null) { // data reference final Data data = root.data(); if (data != null) { final int sl = steps.length; for (int s = 0; s < sl; s++) { if (axisStep(s) == null) return null; } return data; } } return null; }
@Override public FTExpr copy(final QueryContext qc, final VarScope scp, final IntObjMap<Var> vs) { final FTWords ftw = new FTWords( info, query.copy(qc, scp, vs), mode, occ == null ? null : Arr.copyAll(qc, scp, vs, occ)); if (ftt != null) ftw.ftt = ftt.copy(ftw); ftw.tokens = tokens; ftw.data = data; ftw.first = first; ftw.pos = pos; ftw.fast = fast; return ftw; }
@Override public final Expr inline(final QueryContext qc, final VarScope scp, final Var var, final Expr ex) throws QueryException { boolean changed = false; if (root != null) { final Expr rt = root.inline(qc, scp, var, ex); if (rt != null) { root = rt; changed = true; } } final int sl = steps.length; for (int s = 0; s < sl; s++) { final Expr nw = steps[s].inline(qc, scp, var, ex); if (nw != null) { steps[s] = nw; changed = true; } } return changed ? optimize(qc, scp) : null; }
@Override public final int exprSize() { int sz = 1; for (final Expr e : steps) sz += e.exprSize(); return root == null ? sz : sz + root.exprSize(); }
@Override public VarUsage count(final Var var) { final VarUsage inRoot = root == null ? VarUsage.NEVER : root.count(var); return VarUsage.sum(var, steps) == VarUsage.NEVER ? inRoot : VarUsage.MORE_THAN_ONCE; }
@Override public final boolean removable(final Var var) { for (final Expr step : steps) if (step.uses(var)) return false; return root == null || root.removable(var); }
@Override public boolean has(final Flag flag) { if (occ != null) for (final Expr o : occ) if (o.has(flag)) return true; return query.has(flag); }
@Override public boolean removable(final Var var) { if (occ != null) for (final Expr o : occ) if (!o.removable(var)) return false; return query.removable(var); }
@Override public VarUsage count(final Var var) { return occ != null ? VarUsage.sum(var, occ).plus(query.count(var)) : query.count(var); }
@Override public boolean accept(final ASTVisitor visitor) { return super.accept(visitor) && query.accept(visitor) && (occ == null || visitAll(visitor, occ)); }
@Override public boolean removable(final Var v) { return root == null || root.removable(v); }