package org.basex.query.flwor; import static org.basex.query.QueryText.*; import java.io.IOException; import org.basex.io.serial.Serializer; import org.basex.query.QueryContext; import org.basex.query.QueryException; import org.basex.query.expr.And; import org.basex.query.expr.Expr; import org.basex.query.expr.Filter; import org.basex.query.expr.If; import org.basex.query.expr.ParseExpr; import org.basex.query.func.Function; import org.basex.query.item.Empty; import org.basex.query.item.Item; import org.basex.query.item.SeqType; import org.basex.query.iter.Iter; import org.basex.query.path.AxisPath; import org.basex.query.util.ValueList; import org.basex.query.util.Var; import org.basex.util.Array; import org.basex.util.InputInfo; import org.basex.util.list.ObjList; /** * GFLWOR clause. * * @author BaseX Team 2005-12, BSD License * @author Christian Gruen */ public class GFLWOR extends ParseExpr { /** Return expression. */ Expr ret; /** For/Let expression. */ ForLet[] fl; /** Where clause. */ Expr where; /** Order clause. */ private Order order; /** Group by clause. */ private final Group group; /** * GFLWOR constructor. * @param f variable inputs * @param w where clause * @param o order expression * @param g group by expression * @param r return expression * @param ii input info */ GFLWOR(final ForLet[] f, final Expr w, final Order o, final Group g, final Expr r, final InputInfo ii) { super(ii); ret = r; fl = f; where = w; group = g; order = o; } /** * Returns a GFLWOR instance. * @param f variable inputs * @param w where clause * @param o order expression * @param g group by expression * @param r return expression * @param ii input info * @return GFLWOR instance */ public static GFLWOR get(final ForLet[] f, final Expr w, final OrderBy[] o, final Var[][] g, final Expr r, final InputInfo ii) { if(o == null && g == null) return new FLWR(f, w, r, ii); final Order ord = o == null ? null : new Order(ii, o); final Group grp = g == null ? null : new Group(ii, g[0], g[1], g[2]); return new GFLWOR(f, w, ord, grp, r, ii); } @Override public Expr comp(final QueryContext ctx) throws QueryException { compHoist(ctx); compWhere(ctx); final boolean grp = ctx.grouping; ctx.grouping = group != null; // optimize for/let clauses final int vs = ctx.vars.size(); for(int f = 0; f < fl.length; ++f) { final ForLet flt = fl[f]; flt.comp(ctx); // bind variable if it contains a value or occurs only once if(flt.expr.isValue() || count(flt.var, f) == 1) flt.bind(ctx); /* ...or if all inner clauses return only one item. This rewriting would * disallow repeated evaluations of the same expression, but it prevents * index-based rewritings (e.g. for XMark 9) boolean one = true; for(int g = f + 1; g < fl.length; g++) one &= fl[g].size() == 1; if(flt.expr.value() || count(flt.var, f) == 1 && one) flt.bind(ctx); */ } // optimize where clause boolean empty = false; if(where != null) { where = checkUp(where, ctx).comp(ctx).compEbv(ctx); if(where.isValue()) { // test is always false: no results empty = !where.ebv(ctx, input).bool(input); if(!empty) { // always true: test can be skipped ctx.compInfo(OPTREMOVE, description(), where); where = null; } } } if(group != null) group.comp(ctx); if(order != null) order.comp(ctx); ret = ret.comp(ctx); ctx.vars.size(vs); ctx.grouping = grp; // remove FLWOR expression if WHERE clause always returns false if(empty) { ctx.compInfo(OPTREMOVE, description(), where); return Empty.SEQ; } // check if return always yields an empty sequence if(ret == Empty.SEQ) { ctx.compInfo(OPTFLWOR); return ret; } // remove declarations of statically bound or unused variables for(int f = 0; f < fl.length; ++f) { final ForLet l = fl[f]; // do not optimize non-deterministic expressions. example: // let $a := file:write('file', 'content') return ... if(l.var.expr() != null || l.simple(true) && count(l.var, f) == 0 && !l.expr.uses(Use.NDT)) { ctx.compInfo(OPTVAR, l.var); fl = Array.delete(fl, f--); } } // no clauses left: simplify expression // an optional order clause can be safely ignored if(fl.length == 0) { // if where clause exists: where A return B -> if A then B else () // otherwise: return B -> B ctx.compInfo(OPTFLWOR); return where != null ? new If(input, where, ret, Empty.SEQ) : ret; } // remove FLWOR expression if a FOR clause yields an empty sequence for(final ForLet f : fl) { if(f instanceof For && f.size() == 0) { ctx.compInfo(OPTFLWOR); return Empty.SEQ; } } // compute number of results to speed up count() operations if(where == null && group == null) { size = ret.size(); if(size != -1) { // multiply loop runs for(final ForLet f : fl) { final long s = f.size(); if(s == -1) { size = s; break; } size *= s; } } } type = SeqType.get(ret.type().type, size); compHoist(ctx); return this; } /** * Hoists loop-invariant code. Avoids repeated evaluation of independent * variables that return a single value. This method is called twice * (before and after all other optimizations). * @param ctx query context */ private void compHoist(final QueryContext ctx) { // modification counter int m = 0; for(int i = 1; i < fl.length; i++) { final ForLet in = fl[i]; /* move clauses upwards that contain a single value. non-deterministic expressions or fragment constructors creating unique nodes are ignored. example: for $a in 1 to 2 let $b := math:random() return $b */ if(in.size() != 1 || in.uses(Use.NDT) || in.uses(Use.CNS)) continue; // find most outer clause that declares no variables that are used in the // inner clause int p = -1; for(int o = i; o-- != 0 && in.count(fl[o]) == 0; p = o); if(p == -1) continue; // move clause Array.move(fl, p, 1, i - p); fl[p] = in; if(m++ == 0) ctx.compInfo(OPTFORLET); } } /** * Rewrites a where clause to one or more predicates. * @param ctx query context */ private void compWhere(final QueryContext ctx) { // no where clause specified if(where == null) return; // check if all clauses are simple, and if variables are removable for(final ForLet f : fl) { if(f instanceof For && (!f.simple(false) || !where.removable(f.var))) return; } // create array with tests final Expr[] tests = where instanceof And ? ((And) where).expr : new Expr[] { where }; // find which tests access which variables. if a test will not use any of // the variables defined in the local context, they will be added to the // first binding final int[] tar = new int[tests.length]; for(int t = 0; t < tests.length; ++t) { int fr = -1; for(int f = fl.length - 1; f >= 0; --f) { // remember index of most inner FOR clause if(fl[f] instanceof For) fr = f; // predicate is found that uses the current variable if(tests[t].count(fl[f].var) != 0) { // stop rewriting if no most inner FOR clause is defined if(fr == -1) return; // attach predicate to the corresponding FOR clause, and stop tar[t] = fr; break; } } } // convert where clause to predicate(s) ctx.compInfo(OPTWHERE); // bind tests to the corresponding variables for(int t = 0; t < tests.length; ++t) { final ForLet f = fl[tar[t]]; // remove variable reference and optionally wrap test with boolean() Expr e = tests[t].remove(f.var); e = Function.BOOLEAN.get(input, e).compEbv(ctx); // attach predicates to axis path or filter, or create a new filter if(f.expr instanceof AxisPath) { f.expr = ((AxisPath) f.expr).addPreds(e); } else if(f.expr instanceof Filter) { f.expr = ((Filter) f.expr).addPred(e); } else { f.expr = new Filter(input, f.expr, e); } } // eliminate where clause where = null; } @Override public Iter iter(final QueryContext ctx) throws QueryException { final Iter[] iter = new Iter[fl.length]; final int vs = ctx.vars.size(); for(int f = 0; f < fl.length; ++f) iter[f] = ctx.iter(fl[f]); // evaluate pre grouping tuples ObjList<Item[]> keys = null; ValueList vals = null; if(order != null) { keys = new ObjList<Item[]>(); vals = new ValueList(); } if(group != null) group.init(order); iter(ctx, iter, 0, keys, vals); ctx.vars.size(vs); for(final ForLet f : fl) ctx.vars.add(f.var); // order != null, otherwise it would have been handled in group final Iter ir = group != null ? group.gp.ret(ctx, ret, keys, vals) : ctx.iter(order.set(keys, vals)); ctx.vars.size(vs); return ir; } /** * Performs a recursive iteration on the specified variable position. * @param ctx query context * @param it iterator * @param p variable position * @param ks sort keys * @param vs values to sort * @throws QueryException query exception */ private void iter(final QueryContext ctx, final Iter[] it, final int p, final ObjList<Item[]> ks, final ValueList vs) throws QueryException { final boolean more = p + 1 != fl.length; while(it[p].next() != null) { if(more) { iter(ctx, it, p + 1, ks, vs); } else if(where == null || where.ebv(ctx, input).bool(input)) { if(group != null) { group.gp.add(ctx); } else if(order != null) { // order by will be handled in group by otherwise order.add(ctx, ret, ks, vs); } } } } @Override public final boolean uses(final Use u) { return u == Use.VAR || ret.uses(u); } @Override public final int count(final Var v) { return count(v, 0); } /** * Counts how often the specified variable is used, starting from the * specified for/let index. * @param v variable to be checked * @param i index * @return number of occurrences */ final int count(final Var v, final int i) { int c = 0; for(int f = i; f < fl.length; f++) c += fl[f].count(v); if(where != null) c += where.count(v); if(order != null) c += order.count(v); if(group != null) c += group.count(v); return c + ret.count(v); } @Override public final boolean removable(final Var v) { for(final ForLet f : fl) if(!f.removable(v)) return false; return (where == null || where.removable(v)) && (order == null || order.removable(v)) && (group == null || group.removable(v)) && ret.removable(v); } @Override public final Expr remove(final Var v) { for(final ForLet f : fl) f.remove(v); if(where != null) where = where.remove(v); if(order != null) order = order.remove(v); ret = ret.remove(v); return this; } @Override public final void plan(final Serializer ser) throws IOException { ser.openElement(this); for(final ForLet f : fl) f.plan(ser); if(where != null) { ser.openElement(WHR); where.plan(ser); ser.closeElement(); } if(group != null) group.plan(ser); if(order != null) order.plan(ser); ser.openElement(RET); ret.plan(ser); ser.closeElement(); ser.closeElement(); } @Override public final String toString() { final StringBuilder sb = new StringBuilder(); for(int i = 0; i != fl.length; ++i) sb.append(i != 0 ? " " : "").append(fl[i]); if(where != null) sb.append(' ' + WHERE + ' ' + where); if(group != null) sb.append(group); if(order != null) sb.append(order); return sb.append(' ' + RETURN + ' ' + ret).toString(); } }