/** * 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; }
/** * Returns the date in seconds. * * @return seconds */ final BigDecimal seconds() { int z = tz; if (z == Short.MAX_VALUE) { // [CG] XQuery, DateTime: may be removed final long n = System.currentTimeMillis(); z = Calendar.getInstance().getTimeZone().getOffset(n) / 60000; } return (sec == null ? BigDecimal.ZERO : sec) .add(BigDecimal.valueOf(Math.max(0, hou) * 3600 + Math.max(0, min) * 60 - z * 60)); }
@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; }
/** * Adds the specified dayTime duration. * * @param add value to be added */ private void add(final BigDecimal add) { // normalized modulo: sc % 60 vs. (-sc + sc % 60 + 60 + sc) % 60 final BigDecimal sc = sec().add(add); sec = sc.signum() >= 0 ? sc.remainder(BD60) : sc.negate().add(sc.remainder(BD60)).add(BD60).add(sc).remainder(BD60); final long mn = Math.max(min(), 0) + div(sc.longValue(), 60); min = (byte) mod(mn, 60); final long ho = Math.max(hou, 0) + div(mn, 60); hou = (byte) mod(ho, 24); final long da = div(ho, 24); final long[] ymd = ymd(days().add(BigDecimal.valueOf(da))); yea = ymd[0]; mon = (byte) ymd[1]; day = (byte) ymd[2]; }
/** * 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); }
/** * Compiles the filter expression, excluding the root node. * * @param ctx query context * @return compiled expression */ private Expr opt(final QueryContext ctx) { // evaluate return type final SeqType t = root.type(); // determine number of results and type final long s = root.size(); if (s != -1) { if (pos != null) { size = Math.max(0, s + 1 - pos.min) - Math.max(0, s - pos.max); } else if (last) { size = s > 0 ? 1 : 0; } // no results will remain: return empty sequence if (size == 0) return optPre(null, ctx); type = SeqType.get(t.type, size); } else { type = SeqType.get(t.type, t.zeroOrOne() ? Occ.ZERO_ONE : Occ.ZERO_MORE); } // no numeric predicates.. use simple iterator if (!super.has(Flag.FCS)) return new IterFilter(this); // one single position() or last() function specified: return single value if (preds.length == 1 && (last || pos != null) && root.isValue() && t.one() && (last || pos.min == 1 && pos.max == 1)) return optPre(root, ctx); // only choose deterministic and context-independent offsets; e.g., skip: // (1 to 10)[random:integer(10)] or (1 to 10)[.] boolean off = false; if (preds.length == 1) { final Expr p = preds[0]; final SeqType st = p.type(); off = st.type.isNumber() && st.zeroOrOne() && !p.has(Flag.CTX) && !p.has(Flag.NDT); if (off) type = SeqType.get(type.type, Occ.ZERO_ONE); } // iterator for simple numeric predicate return off || useIterator() ? new IterPosFilter(this, off) : this; }
/** * Rounds values. * * @param qc query context * @param even half-to-even flag * @return number * @throws QueryException query exception */ ANum round(final QueryContext qc, final boolean even) throws QueryException { final ANum num = toNumber(exprs[0], qc); final long p = exprs.length == 1 ? 0 : Math.max(Integer.MIN_VALUE, toLong(exprs[1], qc)); return num == null ? null : p > Integer.MAX_VALUE ? num : num.round((int) p, even); }
/** * Evaluates the specified query. * * @param query query * @return success flag */ final boolean query(final String query) { final Performance p = new Performance(); String error; if (exception != null) { error = Util.message(exception); } else { try { long hits = 0; final boolean run = options.get(MainOptions.RUNQUERY); final boolean serial = options.get(MainOptions.SERIALIZE); final int runs = Math.max(1, options.get(MainOptions.RUNS)); for (int r = 0; r < runs; ++r) { // reuse existing processor instance if (r != 0) qp = null; qp(query, context); parse(p); if (r == 0) plan(false); qp.compile(); info.compiling += p.time(); if (r == 0) plan(true); if (!run) continue; final PrintOutput po = r == 0 && serial ? out : new NullOutput(); try (final Serializer ser = qp.getSerializer(po)) { if (maxResults >= 0) { result = qp.cache(maxResults); info.evaluating += p.time(); result.serialize(ser); hits = result.size(); } else { hits = 0; final Iter ir = qp.iter(); info.evaluating += p.time(); for (Item it; (it = ir.next()) != null; ) { ser.serialize(it); ++hits; checkStop(); } } } qp.close(); info.serializing += p.time(); } // dump some query info // out.flush(); // remove string list if global locking is used and if query is updating if (soptions.get(StaticOptions.GLOBALLOCK) && qp.updating) { info.readLocked = null; info.writeLocked = null; } return info(info.toString(qp, out.size(), hits, options.get(MainOptions.QUERYINFO))); } catch (final QueryException | IOException ex) { exception = ex; error = Util.message(ex); } catch (final ProcException ex) { error = INTERRUPTED; } catch (final StackOverflowError ex) { Util.debug(ex); error = BASX_STACKOVERFLOW.desc; } catch (final RuntimeException ex) { extError(""); Util.debug(info()); throw ex; } finally { // close processor after exceptions if (qp != null) qp.close(); } } return extError(error); }
/** * Returns a day count. * * @return days */ final BigDecimal days() { final long y = yea == Long.MAX_VALUE ? 1 : yea; return days(y + ADD_NEG, Math.max(mon, 0), Math.max(day, 0)); }
/** * 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; }