package org.apache.solr.search; import java.text.FieldPosition; import java.text.NumberFormat; import java.text.ParsePosition; import java.text.SimpleDateFormat; import java.util.HashMap; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.TimeZone; import org.apache.lucene.document.FieldType.LegacyNumericType; import org.apache.lucene.queryparser.flexible.aqp.AqpAdsabsQueryTreeBuilder; import org.apache.lucene.queryparser.flexible.aqp.AqpQueryParser; import org.apache.lucene.queryparser.flexible.aqp.builders.AqpAdsabsFunctionProvider; import org.apache.lucene.queryparser.flexible.aqp.builders.AqpAdsabsSubQueryProvider; import org.apache.lucene.queryparser.flexible.aqp.builders.AqpSolrFunctionProvider; import org.apache.lucene.queryparser.flexible.aqp.config.AqpAdsabsQueryConfigHandler; import org.apache.lucene.queryparser.flexible.aqp.config.AqpAdsabsQueryConfigHandler.ConfigurationKeys; import org.apache.lucene.queryparser.flexible.aqp.config.AqpRequestParams; import org.apache.lucene.queryparser.flexible.aqp.parser.AqpStandardQueryConfigHandler; import org.apache.lucene.queryparser.flexible.core.QueryNodeException; import org.apache.lucene.queryparser.flexible.core.QueryNodeParseException; import org.apache.lucene.queryparser.flexible.core.config.QueryConfigHandler; import org.apache.lucene.queryparser.flexible.standard.config.LegacyNumericConfig; import org.apache.lucene.queryparser.flexible.standard.config.NumberDateFormat; import org.apache.lucene.queryparser.flexible.standard.config.StandardQueryConfigHandler; import org.apache.lucene.queryparser.flexible.standard.config.StandardQueryConfigHandler.Operator; import org.apache.lucene.search.Query; import org.apache.solr.common.SolrException; import org.apache.solr.common.params.CommonParams; import org.apache.solr.common.params.SolrParams; import org.apache.solr.request.SolrQueryRequest; import org.apache.solr.schema.IndexSchema; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This is the MAIN solr entry point - this instantiates 'aqp' query * parser - it sets some default parameters from the config and prepares * ulr parameters. * * @see AdsQParserPlugin * @see AqpAdsabsQueryConfigHandler * @see AqpAdsabsQueryTreeBuilder * @see AqpAdsabsQParser * */ public class AqpAdsabsQParser extends QParser { public static TimeZone UTC = TimeZone.getTimeZone("UTC"); public static final Logger log = LoggerFactory .getLogger(AqpAdsabsQParser.class); private AqpQueryParser qParser; public AqpAdsabsQParser(AqpQueryParser parser, String qstr, SolrParams localParams, SolrParams params, SolrQueryRequest req, SolrParserConfigParams defaultConfig) throws QueryNodeParseException { super(qstr, localParams, params, req); qParser = parser; if (getString() == null) { throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "The query is empty"); } IndexSchema schema = req.getSchema(); // now configure the parser using the arguments given in config // and also in the request QueryConfigHandler config = qParser.getQueryConfigHandler(); Map<String, String> namedParams = config.get(AqpStandardQueryConfigHandler.ConfigurationKeys.NAMED_PARAMETER); // get the parameters from the parser configuration (and pass them on) for (Entry<String, String> par: defaultConfig.params.entrySet()) { String k = par.getKey(); if (k.startsWith("aqp.")) { namedParams.put(k, (String) par.getValue()); } } // get the named parameters from solr request object (they will be passed further on) if (params != null) { for (Entry<String, Object> par: params.toNamedList()) { String k = par.getKey(); if (k.startsWith("aqp.")) { namedParams.put(k, (String) par.getValue()); } } } if (localParams != null) { for (Entry<String, Object> par: localParams.toNamedList()) { String k = par.getKey(); if (k.startsWith("aqp.")) { namedParams.put(k, (String) par.getValue()); } } } qParser.setAnalyzer(schema.getQueryAnalyzer()); String defaultField = getParam(CommonParams.DF); if (defaultField == null) { if (namedParams.containsKey("aqp.defaultField")) { defaultField = namedParams.get("aqp.defaultField"); } else { defaultField = getReq().getSchema().getDefaultSearchFieldName(); } } if (defaultField != null) { config.set(AqpStandardQueryConfigHandler.ConfigurationKeys.DEFAULT_FIELD, defaultField); } // if defaultField was set, this will be useless if (namedParams.containsKey("aqp.unfieldedSearchField")) config.set(AqpAdsabsQueryConfigHandler.ConfigurationKeys.UNFIELDED_SEARCH_FIELD, namedParams.get("aqp.unfieldedSearchField")); // default operator String opParam = getParam(QueryParsing.OP); if (opParam == null) { if (namedParams.containsKey("aqp.defaultOperator")) { opParam = namedParams.get("aqp.defaultOperator"); } else { opParam = getReq().getSchema().getQueryParserDefaultOperator(); } } if (opParam != null) { qParser.setDefaultOperator("AND".equals(opParam.toUpperCase()) ? Operator.AND : Operator.OR); } else { throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "The defaultOperator is set to null"); } Map<String, String> fieldMap; for (String fName: new String[]{"aqp.fieldMap", "aqp.fieldMapPostAnalysis"}) { if (fName.equals("aqp.fieldMap")) { // booo fieldMap = config.get(AqpStandardQueryConfigHandler.ConfigurationKeys.FIELD_MAPPER); } else { fieldMap = config.get(AqpStandardQueryConfigHandler.ConfigurationKeys.FIELD_MAPPER_POST_ANALYSIS); } if (namedParams.containsKey(fName)) { String[] fields = namedParams.get(fName).split(";"); String ffs[]; for (String f: fields) { ffs = f.split("\\s+"); if (ffs.length < 2) { throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Configuration error in the section: " + fName); } String target = ffs[ffs.length-1]; for (int i=0;i<ffs.length-1;i++) { fieldMap.put(ffs[i], target); } } } } AqpRequestParams reqAttr = config.get(AqpAdsabsQueryConfigHandler.ConfigurationKeys.SOLR_REQUEST); reqAttr.setQueryString(getString()); reqAttr.setRequest(req); reqAttr.setLocalParams(localParams); reqAttr.setParams(params); // now add the special analyzer that knows to use solr token chains config.set(StandardQueryConfigHandler.ConfigurationKeys.ANALYZER, req.getSchema().getQueryAnalyzer()); config.set(AqpAdsabsQueryConfigHandler.ConfigurationKeys.SOLR_READY, true); if (namedParams.containsKey("aqp.df.fields")) { qParser.setMultiFields(namedParams.get("aqp.df.fields").split(",")); } // special analyzers config.get(ConfigurationKeys.FUNCTION_QUERY_BUILDER_CONFIG).addProvider(0, new AqpSolrFunctionProvider()); config.get(ConfigurationKeys.FUNCTION_QUERY_BUILDER_CONFIG).addProvider(1, new AqpAdsabsSubQueryProvider()); config.get(ConfigurationKeys.FUNCTION_QUERY_BUILDER_CONFIG).addProvider(2, new AqpAdsabsFunctionProvider()); if (namedParams.containsKey("aqp.authorFields")) { for (String f: namedParams.get("aqp.authorFields").split(",")) { config.get(AqpAdsabsQueryConfigHandler.ConfigurationKeys.AUTHOR_FIELDS).put(f, new int[0]); } } if (params.getBool("debugQuery", false) != false) { try { qParser.setDebug(true); } catch (Exception e) { e.printStackTrace(); } } HashMap<String, LegacyNumericConfig> ncm = new HashMap<String, LegacyNumericConfig>(); config.set(StandardQueryConfigHandler.ConfigurationKeys.LEGACY_NUMERIC_CONFIG_MAP, ncm); if (namedParams.containsKey("aqp.floatFields")) { for (String f: namedParams.get("aqp.floatFields").split(",")) { ncm.put(f, new LegacyNumericConfig(8, new MaxNumberFormat(Float.MAX_VALUE), LegacyNumericType.FLOAT)); //UPGRADE: todo } } if (namedParams.containsKey("aqp.dateFields")) { SimpleDateFormat sdf = new SimpleDateFormat(namedParams.containsKey("aqp.dateFormat") ? namedParams.get("aqp.dateFormat") : "yyyy-MM-dd'T'HH:mm:ss", Locale.ROOT); sdf.setTimeZone(UTC); for (String f: namedParams.get("aqp.dateFields").split(",")) { ncm.put(f, new LegacyNumericConfig(6, new MaxNumberFormat(new NumberDateFormat(sdf), Long.MAX_VALUE), LegacyNumericType.LONG)); } } // when precision step=0 (ie use the default solr value), then it is Integer.MAX_VALUE if (namedParams.containsKey("aqp.intFields")) { for (String f: namedParams.get("aqp.intFields").split(",")) { ncm.put(f, new LegacyNumericConfig(Integer.MAX_VALUE, new MaxNumberFormat(Integer.MAX_VALUE), LegacyNumericType.INT)); } } config.get(AqpAdsabsQueryConfigHandler.ConfigurationKeys.VIRTUAL_FIELDS).putAll(defaultConfig.virtualFields); } /** * Internal class that allows us to accept '*' (lucene's way of saying 'anything') */ private class MaxNumberFormat extends NumberFormat { private static final long serialVersionUID = -407706279343648005L; private NumberFormat parser = null; private Number max = null; public MaxNumberFormat(Number max) { this(NumberFormat.getNumberInstance(Locale.US), max); } public MaxNumberFormat(NumberFormat parser, Number max) { this.parser = parser; this.max = max; } @Override public StringBuffer format(double number, StringBuffer toAppendTo, FieldPosition pos) { return parser.format(number, toAppendTo, pos); } @Override public StringBuffer format(long number, StringBuffer toAppendTo, FieldPosition pos) { return parser.format(number, toAppendTo, pos); } @Override public Number parse(String source, ParsePosition parsePosition) { if (source.contains("*")) { parsePosition.setIndex(1); return max; } return parser.parse(source, parsePosition); } } public Query parse() throws SyntaxError { try { //if (qstr.trim().endsWith(",") && !qstr.trim().endsWith("\\,")) { // QueryConfigHandler config = qParser.getQueryConfigHandler(); // return qParser.parse(getString() + config.get(AqpAdsabsQueryConfigHandler.ConfigurationKeys.DUMMY_VALUE), null); //} return qParser.parse(getString(), null); } catch (QueryNodeException e) { throw new SyntaxError(e); } catch (SolrException e1) { throw new SyntaxError(e1); } } public AqpQueryParser getParser() { return qParser; } }