package lux.xpath; import java.util.ArrayList; import lux.SearchResultIterator; import lux.compiler.XPathQuery; import lux.index.FieldRole; import lux.index.IndexConfiguration; import lux.query.BooleanPQuery; import lux.xml.ValueType; import lux.xquery.ElementConstructor; import org.apache.lucene.search.BooleanClause.Occur; import org.apache.lucene.search.SortField; /** * A special search function call; this holds a query that is used to accumulate constraints * while optimizing. The function arguments are inferred from the supplied XPathQuery so as to * result in the same query when evaluated at run time. * TODO: rename either this or the runtime class (lux.functions.SearchBase$SearchCall) of the same name! */ public class SearchCall extends FunCall { private AbstractExpression queryArg; private XPathQuery query; // for facts and sortFields only private boolean fnCollection; private final boolean generated; // records whether this search call was generated by the optimizer public boolean isGenerated() { return generated; } /** * creates a call to lux:search that encodes information provided by the optimizer, enabling combination * with additional filters and sorting criteria * @param query containing the information compiled by the optimizer * @param config used to determine the default field name */ public SearchCall(XPathQuery query, IndexConfiguration config) { this (query.getFullQuery().toXmlNode(config.getDefaultFieldName(), config), query.getFacts(), query.getResultType(), query.getSortFields(), true); } /** used to convert a generic lux:search FunCall into a SearchCall * @param abstractExpression the query, as a string to be parsed by {@link lux.query.parser.LuxQueryParser}, * or as a node to be parsed by {@link lux.query.parser.XmlQueryParser}. */ public SearchCall(AbstractExpression abstractExpression) { this (abstractExpression, XPathQuery.MINIMAL|XPathQuery.SINGULAR, ValueType.VALUE, null, false); } private SearchCall(AbstractExpression queryArg, long facts, ValueType resultType, SortField[] sortFields, boolean isGenerated) { super(FunCall.LUX_SEARCH, resultType); this.queryArg = queryArg; fnCollection = false; query = XPathQuery.getQuery(null, null, facts, resultType, null, sortFields); this.generated = isGenerated; generateArguments(); } public void combineQuery(XPathQuery additionalQuery, IndexConfiguration config) { ElementConstructor additional = additionalQuery.getFullQuery().toXmlNode(config.getDefaultFieldName(), config); if (! additional.getName().getLocalPart().equals("MatchAllDocsQuery")) { if (queryArg.getType() == Type.ELEMENT) { ElementConstructor addClause = new ElementConstructor(BooleanPQuery.CLAUSE_QNAME, additional, BooleanPQuery.MUST_OCCUR_ATT); ElementConstructor thisClause = new ElementConstructor(BooleanPQuery.CLAUSE_QNAME, queryArg, BooleanPQuery.MUST_OCCUR_ATT); ElementConstructor combined = new ElementConstructor(BooleanPQuery.BOOLEAN_QUERY_QNAME, new Sequence (thisClause, addClause)); queryArg = combined; } } // TODO: combine optimizer constraints with user-defined (string) queries this.query = this.query.combineBooleanQueries(Occur.MUST, additionalQuery, Occur.MUST, this.query.getResultType(), config); generateArguments(); } private void generateArguments () { ArrayList<AbstractExpression> args = new ArrayList<AbstractExpression>(); args.add (queryArg); SortField[] sortFields = query.getSortFields(); if (sortFields != null) { args.add(createSortArgument(sortFields)); } else if (! generated) { // if this is an explicit function call that has no explicit ordering, order by relevance args.add(new LiteralExpression (FieldRole.LUX_SCORE + " descending")); } else { args.add(new LiteralExpression (FieldRole.LUX_DOCID)); } subs = args.toArray(new AbstractExpression[args.size()]); } /** * @return a string describing sort options to be passed as an argument to search */ private AbstractExpression createSortArgument (SortField[] sort) { if (sort == null || sort.length == 0) { return LiteralExpression.EMPTY; } if (sort.length == 1) { return new LiteralExpression (formatSortCriterion(sort[0])); } AbstractExpression [] criteria = new AbstractExpression[sort.length]; for (int i = 0; i < sort.length; i++) { criteria[i] = new LiteralExpression (formatSortCriterion(sort[i])); } return new Sequence(criteria); } private String formatSortCriterion(SortField sortField) { StringBuilder buf = new StringBuilder(); buf.append (sortField.getField()); if (sortField.getReverse()) { buf.append (" descending"); } if (SearchResultIterator.MISSING_LAST.equals(sortField.getComparatorSource())) { buf.append (" empty greatest"); } switch (sortField.getType()) { case INT: buf.append(" int"); break; case LONG: buf.append(" long"); break; default: // default is string } return buf.toString(); } /** * @return whether this function call will be represented by fn:collection("lux:" + query) */ public boolean isFnCollection() { return fnCollection; } public void setFnCollection(boolean isFnCollection) { this.fnCollection = isFnCollection; } @Override public SearchCall getRoot() { return this; } public XPathQuery getQuery () { return query; } }