/** * Evaluates the full-text match. * * @param qc query context * @return number of tokens, used for scoring * @throws QueryException query exception */ private int contains(final QueryContext qc) throws QueryException { first = true; final FTLexer lexer = ftt.lexer(qc.ftToken); // use faster evaluation for default options int num = 0; if (fast) { for (final byte[] t : tokens) { final FTTokens qtok = ftt.cache(t); num = Math.max(num, ftt.contains(qtok, lexer) * qtok.length()); } return num; } // find and count all occurrences final boolean all = mode == FTMode.ALL || mode == FTMode.ALL_WORDS; int oc = 0; for (final byte[] w : unique(tokens(qc))) { final FTTokens qtok = ftt.cache(w); final int o = ftt.contains(qtok, lexer); if (all && o == 0) return 0; num = Math.max(num, o * qtok.length()); oc += o; } // check if occurrences are in valid range. if yes, return number of tokens final long mn = occ != null ? toLong(occ[0], qc) : 1; final long mx = occ != null ? toLong(occ[1], qc) : Long.MAX_VALUE; if (mn == 0 && oc == 0) matches = FTNot.not(matches); return oc >= mn && oc <= mx ? Math.max(1, num) : 0; }
@Override public boolean indexAccessible(final IndexInfo ii) { /* If the following conditions yield true, the index is accessed: * - all query terms are statically available * - no FTTimes option is specified * - explicitly set case, diacritics and stemming match options do not * conflict with index options. */ data = ii.ic.data; final MetaData md = data.meta; final FTOpt fto = ftt.opt; /* Index will be applied if no explicit match options have been set * that conflict with the index options. As a consequence, though, index- * based querying might yield other results than sequential scanning. */ if (occ != null || fto.cs != null && md.casesens == (fto.cs == FTCase.INSENSITIVE) || fto.isSet(DC) && md.diacritics != fto.is(DC) || fto.isSet(ST) && md.stemming != fto.is(ST) || fto.ln != null && !fto.ln.equals(md.language)) return false; // adopt database options to tokenizer fto.copy(md); // estimate costs if text is not known at compile time if (tokens == null) { ii.costs = Math.max(2, data.meta.size / 30); return true; } // summarize number of hits; break loop if no hits are expected final FTLexer ft = new FTLexer(fto); ii.costs = 0; for (byte[] t : tokens) { ft.init(t); while (ft.hasNext()) { final byte[] tok = ft.nextToken(); if (fto.sw != null && fto.sw.contains(tok)) continue; if (fto.is(WC)) { // don't use index if one of the terms starts with a wildcard t = ft.get(); if (t[0] == '.') return false; // don't use index if certain characters or more than 1 dot are found int d = 0; for (final byte w : t) { if (w == '{' || w == '\\' || w == '.' && ++d > 1) return false; } } // favor full-text index requests over exact queries final int costs = data.costs(ft); if (costs != 0) ii.costs += Math.max(2, costs / 100); } } return true; }
/** * Finds line and column for the specified query parser. * * @param parser parser */ void pos(final InputParser parser) { markedCol = parser.mark; if (info != null) return; // check if line/column information has already been added parser.pos = Math.min(parser.mark, parser.length); info = new InputInfo(parser); }
/** * Merges two matches. * * @param i1 first item * @param i2 second item */ private static void and(final FTNode i1, final FTNode i2) { final FTMatches all = new FTMatches((byte) Math.max(i1.matches().pos, i2.matches().pos)); for (final FTMatch s1 : i1.matches()) { for (final FTMatch s2 : i2.matches()) { all.add(new FTMatch(s1.size() + s2.size()).add(s1).add(s2)); } } i1.score(Scoring.avg(i1.score() + i2.score(), 2)); i1.matches(all); }
@Override void calcSize(final long[] minMax) { minMax[0] = Math.min(minMax[0], 1); }
/** * Formats the specified number and returns a string representation. * * @param item item * @param pics pictures * @param ii input info * @return picture variables * @throws QueryException query exception */ private byte[] format(final ANum item, final Picture[] pics, final InputInfo ii) throws QueryException { // Rule 1: return results for NaN final double d = item.dbl(ii); if (Double.isNaN(d)) return nan; // Rule 2: check if value if negative (smaller than zero or -0) final boolean neg = d < 0 || d == 0 && Double.doubleToLongBits(d) == Long.MIN_VALUE; final Picture pic = pics[neg && pics.length == 2 ? 1 : 0]; final IntList res = new IntList(), intgr = new IntList(), fract = new IntList(); int exp = 0; // Rule 3: percent/permille ANum num = item; if (pic.pc) num = (ANum) Calc.MULT.ev(num, Int.get(100), ii); if (pic.pm) num = (ANum) Calc.MULT.ev(num, Int.get(1000), ii); if (Double.isInfinite(num.dbl(ii))) { // Rule 4: infinity intgr.add(new TokenParser(inf).toArray()); } else { // Rule 5: exponent if (pic.minExp != 0 && d != 0) { BigDecimal dec = num.dec(ii).abs().stripTrailingZeros(); int scl = 0; if (dec.compareTo(BigDecimal.ONE) >= 0) { scl = dec.setScale(0, RoundingMode.HALF_DOWN).precision(); } else { while (dec.compareTo(BigDecimal.ONE) < 0) { dec = dec.multiply(BigDecimal.TEN); scl--; } scl++; } exp = scl - pic.min[0]; if (exp != 0) { final BigDecimal n = BigDecimal.TEN.pow(Math.abs(exp)); num = (ANum) Calc.MULT.ev(num, Dec.get(exp > 0 ? BigDecimal.ONE.divide(n) : n), ii); } } num = num.round(pic.maxFrac, true).abs(); // convert positive number to string final String s = (num instanceof Dbl || num instanceof Flt ? Dec.get(BigDecimal.valueOf(num.dbl(ii))) : num) .toString(); // integer/fractional separator final int sep = s.indexOf('.'); // create integer part final int sl = s.length(); final int il = sep == -1 ? sl : sep; for (int i = il; i < pic.min[0]; ++i) intgr.add(zero); // fractional number: skip leading 0 if (!s.startsWith("0.") || pic.min[0] > 0) { for (int i = 0; i < il; i++) intgr.add(zero + s.charAt(i) - '0'); } // squeeze in grouping separators if (pic.group[0].length == 1 && pic.group[0][0] > 0) { // regular pattern with repeating separators for (int p = intgr.size() - (neg ? 2 : 1); p > 0; --p) { if (p % pic.group[0][0] == 0) intgr.insert(intgr.size() - p, grouping); } } else { // irregular pattern, or no separators at all final int gl = pic.group[0].length; for (int g = 0; g < gl; ++g) { final int pos = intgr.size() - pic.group[0][g]; if (pos > 0) intgr.insert(pos, grouping); } } // create fractional part final int fl = sep == -1 ? 0 : sl - il - 1; if (fl != 0) for (int i = sep + 1; i < sl; i++) fract.add(zero + s.charAt(i) - '0'); for (int i = fl; i < pic.min[1]; ++i) fract.add(zero); // squeeze in grouping separators in a reverse manner final int ul = fract.size(); for (int p = pic.group[1].length - 1; p >= 0; p--) { final int pos = pic.group[1][p]; if (pos < ul) fract.insert(pos, grouping); } } // add minus sign if (neg && pics.length != 2) res.add(minus); // add prefix and integer part res.add(pic.prefSuf[0].toArray()).add(intgr.finish()); // add fractional part if (!fract.isEmpty()) res.add(decimal).add(fract.finish()); // add exponent if (pic.minExp != 0) { res.add(exponent); if (exp < 0) res.add(minus); final String s = Integer.toString(Math.abs(exp)); final int sl = s.length(); for (int i = sl; i < pic.minExp; i++) res.add(zero); for (int i = 0; i < sl; i++) res.add(zero + s.charAt(i) - '0'); } // add suffix res.add(pic.prefSuf[1].toArray()); return new TokenBuilder(res.finish()).finish(); }
/** * Analyzes the specified patterns. * * @param patterns patterns * @return picture variables */ private Picture[] analyze(final byte[][] patterns) { // pictures final int picL = patterns.length; final Picture[] pics = new Picture[picL]; // analyze patterns for (int p = 0; p < picL; p++) { final byte[] pt = patterns[p]; final Picture pic = new Picture(); // position (integer/fractional) int pos = 0; // active character found boolean act = false; // number of characters after exponent int exp = -1; // number of optional characters final int[] opt = new int[2]; // loop through all characters final int pl = pt.length; for (int i = 0, cl; i < pl; i += cl) { final int ch = ch(pt, i); cl = cl(pt, i); boolean active = contains(actives, ch); if (ch == decimal) { ++pos; act = false; } else if (ch == optional) { opt[pos]++; } else if (ch == exponent) { if (act && containsActive(pt, i + cl)) { exp = 0; } else { active = false; } } else if (ch == grouping) { if (pos == 0) pic.group[pos] = Array.add(pic.group[pos], pic.min[pos] + opt[pos]); } else if (contains(digits, ch)) { if (exp == -1) pic.min[pos]++; else exp++; } if (active) { act = true; } else { // passive characters pic.pc |= ch == percent; pic.pm |= ch == permille; // prefixes/suffixes pic.prefSuf[pos == 0 && act ? pos + 1 : pos].add(ch); } } // finalize integer-part-grouping-positions final int[] igp = pic.group[0]; final int igl = igp.length; for (int g = 0; g < igl; ++g) igp[g] = pic.min[0] + opt[0] - igp[g]; // check if integer-part-grouping-positions are regular // if yes, they are replaced with a single position if (igl > 1) { boolean reg = true; final int i = igp[igl - 1]; for (int g = igl - 2; g >= 0; --g) reg &= i * igl == igp[g]; if (reg) pic.group[0] = new int[] {i}; } pic.maxFrac = pic.min[1] + opt[1]; pic.minExp = Math.max(0, exp); pics[p] = pic; } return pics; }