org.apache.solr.search.QueryParsing.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.solr.search.QueryParsing.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.solr.search;

import org.apache.lucene.index.Term;
import org.apache.solr.legacy.LegacyNumericRangeQuery;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.BoostQuery;
import org.apache.lucene.search.ConstantScoreQuery;
import org.apache.lucene.search.FuzzyQuery;
import org.apache.lucene.search.PrefixQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.TermRangeQuery;
import org.apache.lucene.search.WildcardQuery;
import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.CharsRefBuilder;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.parser.QueryParser;
import org.apache.solr.schema.FieldType;
import org.apache.solr.schema.IndexSchema;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * Collection of static utilities useful for query parsing.
 *
 *
 */
public class QueryParsing {
    public static final String OP = "q.op"; // the SolrParam used to override the QueryParser "default operator"
    public static final String V = "v"; // value of this parameter
    public static final String F = "f"; // field that a query or command pertains to
    public static final String TYPE = "type";// parser for this query or command
    public static final String DEFTYPE = "defType"; // default parser for any direct subqueries
    public static final String SPLIT_ON_WHITESPACE = "sow"; // Whether to split on whitespace prior to analysis
    public static final String LOCALPARAM_START = "{!";
    public static final char LOCALPARAM_END = '}';
    // true if the value was specified by the "v" param (i.e. v=myval, or v=$param)
    public static final String VAL_EXPLICIT = "__VAL_EXPLICIT__";

    /**
     * @param txt Text to parse
     * @param start Index into text for start of parsing
     * @param target Object to inject with parsed settings
     * @param params Additional existing parameters
     */
    public static int parseLocalParams(String txt, int start, ModifiableSolrParams target, SolrParams params)
            throws SyntaxError {
        return parseLocalParams(txt, start, target, params, LOCALPARAM_START, LOCALPARAM_END);
    }

    /**
     * @param txt Text to parse
     * @param start Index into text for start of parsing
     * @param target Object to inject with parsed settings
     * @param params Additional existing parameters
     * @param startString String that indicates the start of a localParams section
     * @param endChar Character that indicates the end of a localParams section
     */
    public static int parseLocalParams(String txt, int start, ModifiableSolrParams target, SolrParams params,
            String startString, char endChar) throws SyntaxError {
        int off = start;
        if (!txt.startsWith(startString, off))
            return start;
        StrParser p = new StrParser(txt, start, txt.length());
        p.pos += startString.length(); // skip over "{!"

        for (;;) {
            /*
            if (p.pos>=txt.length()) {
              throw new SyntaxError("Missing '}' parsing local params '" + txt + '"');
            }
            */
            char ch = p.peek();
            if (ch == endChar) {
                return p.pos + 1;
            }

            String id = p.getId();
            if (id.length() == 0) {
                throw new SyntaxError(
                        "Expected ending character '" + endChar + "' parsing local params '" + txt + '"');

            }
            String[] val = new String[1];

            ch = p.peek();
            if (ch != '=') {
                // single word... treat {!func} as type=func for easy lookup
                val[0] = id;
                id = TYPE;
            } else {
                // saw equals, so read value
                p.pos++;
                ch = p.peek();
                boolean deref = false;
                if (ch == '$') {
                    p.pos++;
                    ch = p.peek();
                    deref = true; // dereference whatever value is read by treating it as a variable name
                }

                if (ch == '\"' || ch == '\'') {
                    val[0] = p.getQuotedString();
                } else {
                    // read unquoted literal ended by whitespace or endChar (normally '}')
                    // there is no escaping.
                    int valStart = p.pos;
                    for (;;) {
                        if (p.pos >= p.end) {
                            throw new SyntaxError(
                                    "Missing end to unquoted value starting at " + valStart + " str='" + txt + "'");
                        }
                        char c = p.val.charAt(p.pos);
                        if (c == endChar || Character.isWhitespace(c)) {
                            val[0] = p.val.substring(valStart, p.pos);
                            break;
                        }
                        p.pos++;
                    }
                }

                if (deref) { // dereference parameter
                    if (params != null) {
                        val = params.getParams(val[0]);
                    }
                }
            }
            if (target != null)
                target.add(id, val);
        }
    }

    /**
     * "foo" returns null
     * "{!prefix f=myfield}yes" returns type="prefix",f="myfield",v="yes"
     * "{!prefix f=myfield v=$p}" returns type="prefix",f="myfield",v=params.get("p")
     */
    public static SolrParams getLocalParams(String txt, SolrParams params) throws SyntaxError {
        if (txt == null || !txt.startsWith(LOCALPARAM_START)) {
            return null;
        }
        ModifiableSolrParams localParams = new ModifiableSolrParams();
        int start = QueryParsing.parseLocalParams(txt, 0, localParams, params);

        String val = localParams.get(V);
        if (val == null) {
            val = txt.substring(start);
            localParams.set(V, val);
        } else {
            // localParams.put(VAL_EXPLICIT, "true");
        }
        return localParams;
    }

    ///////////////////////////
    ///////////////////////////
    ///////////////////////////

    static FieldType writeFieldName(String name, IndexSchema schema, Appendable out, int flags) throws IOException {
        FieldType ft = null;
        ft = schema.getFieldTypeNoEx(name);
        out.append(name);
        if (ft == null) {
            out.append("(UNKNOWN FIELD ").append(name).append(String.valueOf(')'));
        }
        out.append(':');
        return ft;
    }

    static void writeFieldVal(String val, FieldType ft, Appendable out, int flags) throws IOException {
        if (ft != null) {
            try {
                out.append(ft.indexedToReadable(val));
            } catch (Exception e) {
                out.append("EXCEPTION(val=");
                out.append(val);
                out.append(")");
            }
        } else {
            out.append(val);
        }
    }

    static void writeFieldVal(BytesRef val, FieldType ft, Appendable out, int flags) throws IOException {
        if (ft != null) {
            try {
                CharsRefBuilder readable = new CharsRefBuilder();
                ft.indexedToReadable(val, readable);
                out.append(readable.get());
            } catch (Exception e) {
                out.append("EXCEPTION(val=");
                out.append(val.utf8ToString());
                out.append(")");
            }
        } else {
            out.append(val.utf8ToString());
        }
    }

    private static int FLAG_BOOSTED = 0x01;
    private static int FLAG_IS_CLAUSE = 0x02;

    /**
     * @see #toString(Query,IndexSchema)
     */
    public static void toString(Query query, IndexSchema schema, Appendable out, int flags) throws IOException {
        int subflag = flags & ~(FLAG_BOOSTED | FLAG_IS_CLAUSE); // clear the boosted / is clause flags for recursion

        if (query instanceof TermQuery) {
            TermQuery q = (TermQuery) query;
            Term t = q.getTerm();
            FieldType ft = writeFieldName(t.field(), schema, out, flags);
            writeFieldVal(t.bytes(), ft, out, flags);
        } else if (query instanceof TermRangeQuery) {
            TermRangeQuery q = (TermRangeQuery) query;
            String fname = q.getField();
            FieldType ft = writeFieldName(fname, schema, out, flags);
            out.append(q.includesLower() ? '[' : '{');
            BytesRef lt = q.getLowerTerm();
            BytesRef ut = q.getUpperTerm();
            if (lt == null) {
                out.append('*');
            } else {
                writeFieldVal(lt, ft, out, flags);
            }

            out.append(" TO ");

            if (ut == null) {
                out.append('*');
            } else {
                writeFieldVal(ut, ft, out, flags);
            }

            out.append(q.includesUpper() ? ']' : '}');
        } else if (query instanceof LegacyNumericRangeQuery) {
            LegacyNumericRangeQuery q = (LegacyNumericRangeQuery) query;
            String fname = q.getField();
            FieldType ft = writeFieldName(fname, schema, out, flags);
            out.append(q.includesMin() ? '[' : '{');
            Number lt = q.getMin();
            Number ut = q.getMax();
            if (lt == null) {
                out.append('*');
            } else {
                out.append(lt.toString());
            }

            out.append(" TO ");

            if (ut == null) {
                out.append('*');
            } else {
                out.append(ut.toString());
            }

            out.append(q.includesMax() ? ']' : '}');
        } else if (query instanceof BooleanQuery) {
            BooleanQuery q = (BooleanQuery) query;
            boolean needParens = false;

            if (q.getMinimumNumberShouldMatch() != 0 || (flags & (FLAG_IS_CLAUSE | FLAG_BOOSTED)) != 0) {
                needParens = true;
            }
            if (needParens) {
                out.append('(');
            }
            boolean first = true;
            for (BooleanClause c : q.clauses()) {
                if (!first) {
                    out.append(' ');
                } else {
                    first = false;
                }

                if (c.isProhibited()) {
                    out.append('-');
                } else if (c.isRequired()) {
                    out.append('+');
                }
                Query subQuery = c.getQuery();

                toString(subQuery, schema, out, subflag | FLAG_IS_CLAUSE);

            }

            if (needParens) {
                out.append(')');
            }
            if (q.getMinimumNumberShouldMatch() > 0) {
                out.append('~');
                out.append(Integer.toString(q.getMinimumNumberShouldMatch()));
            }

        } else if (query instanceof PrefixQuery) {
            PrefixQuery q = (PrefixQuery) query;
            Term prefix = q.getPrefix();
            FieldType ft = writeFieldName(prefix.field(), schema, out, flags);
            out.append(prefix.text());
            out.append('*');
        } else if (query instanceof WildcardQuery) {
            out.append(query.toString());
        } else if (query instanceof FuzzyQuery) {
            out.append(query.toString());
        } else if (query instanceof ConstantScoreQuery) {
            out.append(query.toString());
        } else if (query instanceof WrappedQuery) {
            WrappedQuery q = (WrappedQuery) query;
            out.append(q.getOptions());
            toString(q.getWrappedQuery(), schema, out, subflag);
        } else if (query instanceof BoostQuery) {
            BoostQuery q = (BoostQuery) query;
            toString(q.getQuery(), schema, out, subflag | FLAG_BOOSTED);
            out.append('^');
            out.append(Float.toString(q.getBoost()));
        } else {
            out.append(query.getClass().getSimpleName()).append('(').append(query.toString()).append(')');
        }
    }

    /**
     * Formats a Query for debugging, using the IndexSchema to make
     * complex field types readable.
     * <p>
     * The benefit of using this method instead of calling
     * <code>Query.toString</code> directly is that it knows about the data
     * types of each field, so any field which is encoded in a particularly
     * complex way is still readable. The downside is that it only knows
     * about built in Query types, and will not be able to format custom
     * Query classes.
     * </p>
     */
    public static String toString(Query query, IndexSchema schema) {
        try {
            StringBuilder sb = new StringBuilder();
            toString(query, schema, sb, 0);
            return sb.toString();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Builds a list of String which are stringified versions of a list of Queries
     */
    public static List<String> toString(List<Query> queries, IndexSchema schema) {
        List<String> out = new ArrayList<>(queries.size());
        for (Query q : queries) {
            out.add(QueryParsing.toString(q, schema));
        }
        return out;
    }

    /**
     * Parses default operator string into Operator object
     * @param operator the string from request
     * @return Operator.AND if string equals "AND", else return Operator.OR (default)
     */
    public static QueryParser.Operator parseOP(String operator) {
        return "and".equalsIgnoreCase(operator) ? QueryParser.Operator.AND : QueryParser.Operator.OR;
    }
}