package org.basex.query.expr; import static org.basex.query.QueryText.*; import java.io.IOException; import org.basex.data.MemData; import org.basex.index.IndexToken.IndexType; import org.basex.index.StatsType; import org.basex.index.Names; import org.basex.index.RangeToken; import org.basex.index.Stats; import org.basex.io.serial.Serializer; import org.basex.query.QueryContext; import org.basex.query.QueryException; import org.basex.query.item.Bln; import org.basex.query.item.Item; import org.basex.query.item.NodeType; import org.basex.query.item.SeqType; import org.basex.query.iter.Iter; import org.basex.query.path.Axis; import org.basex.query.path.AxisPath; import org.basex.query.path.AxisStep; import org.basex.query.path.NameTest; import org.basex.query.path.Test.Name; import org.basex.query.util.IndexContext; import org.basex.util.InputInfo; import org.basex.util.Token; /** * Range comparison expression. * * @author BaseX Team 2005-12, BSD License * @author Christian Gruen */ public final class CmpR extends Single { /** Minimum. */ private final double min; /** Include minimum value. */ private final boolean mni; /** Maximum. */ private final double max; /** Include maximum value. */ private final boolean mxi; /** Index container. */ private RangeToken rt; /** Flag for atomic evaluation. */ private final boolean atomic; /** * Constructor. * @param ii input info * @param e (compiled) expression * @param mn minimum value * @param in include minimum value * @param mx maximum value * @param ix include maximum value */ private CmpR(final InputInfo ii, final Expr e, final double mn, final boolean in, final double mx, final boolean ix) { super(ii, e); min = mn; mni = in; max = mx; mxi = ix; type = SeqType.BLN; atomic = e.type().zeroOrOne(); } /** * Tries to convert the specified expression into a range expression. * @param ex expression * @return resulting or specified expression * @throws QueryException query exception */ static Expr get(final CmpG ex) throws QueryException { if(!ex.expr[1].isItem()) return ex; final Item it = (Item) ex.expr[1]; if(!it.type.isNumber()) return ex; final Expr e = ex.expr[0]; final double d = it.dbl(ex.input); switch(ex.op.op) { case EQ: return new CmpR(ex.input, e, d, true, d, true); case GE: return new CmpR(ex.input, e, d, true, Double.POSITIVE_INFINITY, true); case GT: return new CmpR(ex.input, e, d, false, Double.POSITIVE_INFINITY, true); case LE: return new CmpR(ex.input, e, Double.NEGATIVE_INFINITY, true, d, true); case LT: return new CmpR(ex.input, e, Double.NEGATIVE_INFINITY, true, d, false); default: return ex; } } @Override public Bln item(final QueryContext ctx, final InputInfo ii) throws QueryException { // atomic evaluation of arguments (faster) if(atomic) { final Item it = expr.item(ctx, input); if(it == null) return Bln.FALSE; final double d = it.dbl(input); return Bln.get((mni ? d >= min : d > min) && (mxi ? d <= max : d < max)); } // iterative evaluation final Iter ir = ctx.iter(expr); for(Item it; (it = ir.next()) != null;) { final double d = it.dbl(input); if((mni ? d >= min : d > min) && (mxi ? d <= max : d < max)) return Bln.TRUE; } return Bln.FALSE; } /** * Creates an intersection of the existing and the specified expressions. * @param c range comparison * @return resulting expression or {@code null} */ Expr intersect(final CmpR c) { if(!c.expr.sameAs(expr)) return null; final double mn = Math.max(min, c.min); final double mx = Math.min(max, c.max); return mn > mx ? Bln.FALSE : new CmpR(input, c.expr, mn, mni && c.mni, mx, mxi && c.mxi); } @Override public boolean indexAccessible(final IndexContext ic) { // accept only location path, string and equality expressions final AxisStep s = CmpG.indexStep(expr); // sequential main memory is assumed to be faster than range index access if(s == null || ic.data instanceof MemData) return false; // check which index applies final boolean text = s.test.type == NodeType.TXT && ic.data.meta.textindex; final boolean attr = s.test.type == NodeType.ATT && ic.data.meta.attrindex; if(!text && !attr || !mni || !mxi) return false; final Stats key = key(ic, text); if(key == null) return false; // estimate costs for range access; all values out of range: no results rt = new RangeToken(text, Math.max(min, key.min), Math.min(max, key.max)); ic.costs(rt.min > rt.max || rt.max < key.min || rt.min > key.max ? 0 : Math.max(1, ic.data.meta.size / 5)); // use index if costs are zero, or if min/max is not infinite return ic.costs() == 0 || min != Double.NEGATIVE_INFINITY && max != Double.POSITIVE_INFINITY; } @Override public Expr indexEquivalent(final IndexContext ic) { final boolean text = rt.type() == IndexType.TEXT; ic.ctx.compInfo(OPTRNGINDEX); return ic.invert(expr, new RangeAccess(input, rt, ic), text); } /** * Retrieves the statistics key for the tag/attribute name. * @param ic index context * @param text text flag * @return key */ private Stats key(final IndexContext ic, final boolean text) { // statistics are not up-to-date if(!ic.data.meta.uptodate || ic.data.nspaces.size() != 0) return null; final AxisPath path = (AxisPath) expr; final int st = path.steps.length; final AxisStep step; if(text) { step = st == 1 ? ic.step : path.step(st - 2); if(!(step.test.test == Name.NAME)) return null; } else { step = path.step(st - 1); if(!step.simple(Axis.ATTR, true)) return null; } final Names names = text ? ic.data.tagindex : ic.data.atnindex; final Stats key = names.stat(names.id(((NameTest) step.test).ln)); return key == null || key.type == StatsType.INTEGER || key.type == StatsType.DOUBLE ? key : null; } @Override public void plan(final Serializer ser) throws IOException { ser.openElement(this, MIN, Token.token(min), MAX, Token.token(max)); expr.plan(ser); ser.closeElement(); } @Override public String toString() { final StringBuilder sb = new StringBuilder(); if(min != Double.NEGATIVE_INFINITY) sb.append(min).append(mni ? " <= " : " < "); sb.append(expr); if(max != Double.POSITIVE_INFINITY) sb.append(mxi ? " <= " : " < ").append(max); return sb.toString(); } }