/** * */ package querqy.solr; import java.io.IOException; import java.util.*; import java.util.regex.Pattern; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.queries.function.BoostedQuery; import org.apache.lucene.queries.function.ValueSource; import org.apache.lucene.queries.function.valuesource.ProductFloatFunction; import org.apache.lucene.search.BooleanClause; import org.apache.lucene.search.BooleanClause.Occur; import org.apache.lucene.search.BooleanQuery; import org.apache.lucene.search.MatchAllDocsQuery; import org.apache.lucene.search.Query; import org.apache.lucene.util.QueryBuilder; import org.apache.solr.common.SolrException; import org.apache.solr.common.params.CommonParams; import org.apache.solr.common.params.DisMaxParams; import org.apache.solr.common.params.SolrParams; import org.apache.solr.request.SolrQueryRequest; import org.apache.solr.schema.FieldType; import org.apache.solr.schema.IndexSchema; import org.apache.solr.schema.SchemaField; import org.apache.solr.schema.TextField; import org.apache.solr.search.*; import org.apache.solr.util.SolrPluginUtils; import querqy.lucene.LuceneQueryUtil; import querqy.ComparableCharSequence; import querqy.lucene.rewrite.*; import querqy.lucene.rewrite.SearchFieldsAndBoosting.FieldBoostModel; import querqy.lucene.rewrite.cache.TermQueryCache; import querqy.model.BoostQuery; import querqy.model.DisjunctionMaxClause; import querqy.model.DisjunctionMaxQuery; import querqy.model.ExpandedQuery; import querqy.model.QuerqyQuery; import querqy.model.RawQuery; import querqy.model.Term; import querqy.parser.QuerqyParser; import querqy.rewrite.ContextAwareQueryRewriter; import querqy.rewrite.RewriteChain; /** * @author rene * */ public class QuerqyDismaxQParser extends ExtendedDismaxQParser { /** * generated field boost */ public static final String GFB = "gfb"; /** * generated query fields (query generated terms only in these fields) */ public static final String GQF = "gqf"; /** * field boost model */ public static final String FBM = "fbm"; /** * Query-independent field boost model, reading field boost factors from request params */ public static final String FBM_FIXED = "fixed"; /** * Query-dependent field boost model ('Probabilistic Retrieval Model for Semi-structured Data') */ public static final String FBM_PRMS = "prms"; /** * The default field boost model (= {@link #FBM_FIXED}) */ public static final String FBM_DEFAULT = FBM_FIXED; /** * boost method - the method to integrate Querqy boost queries with the main query */ public static final String QBOOST_METHOD = "qboost.method"; /** * Integrate Querqy boost queries with the main query as a re-rank query */ public static final String QBOOST_METHOD_RERANK = "rerank"; /** * The number of docs in the main query result to use for re-ranking when qboost.method=rerank */ public static final String QBOOST_RERANK_NUMDOCS = "qboost.rerank.numDocs"; /** * The default value for {@link #QBOOST_RERANK_NUMDOCS} */ public static final int DEFAULT_RERANK_NUMDOCS = 500; /** * Add Querqy boost queries to the main query as optional boolean clauses */ public static final String QBOOST_METHOD_OPT = "opt"; /** * The default boost method (= {@link #QBOOST_METHOD}) */ public static final String QBOOST_METHOD_DEFAULT = QBOOST_METHOD_OPT; /** * Control how the score resulting from the {@link org.apache.lucene.search.similarities.Similarity} * implementation is integrated into the score of a Querqy boost query */ public static final String QBOOST_SIMILARITY_SCORE = "qboost.similarityScore"; /** * A possible value of QBOOST_SIMILARITY_SCORE: Do not calculate the similarity score for Querqy boost queries. * As a result the boost queries are only scored by query boost and field boost but not by any function of DF or TF. * Setting qboost.similarityScore=off yields a small performance gain as TF and DF need not be provided. */ public static final String QBOOST_SIMILARITY_SCORE_OFF = "off"; /** * Tie parameter for combining pf, pf2 and pf3 phrase boostings into a dismax query. Defaults to the value * of the {@link org.apache.solr.common.params.DisMaxParams.TIE} parameter */ public static final String QPF_TIE = "qpf.tie"; /** * A possible value of QBOOST_SIMILARITY_SCORE: Just use the similarity as set in Solr when scoring Querqy boost queries. */ public static final String QBOOST_SIMILARITY_SCORE_ON = "on"; /** * A possible value of QBOOST_SIMILARITY_SCORE: "document frequency correction" - use the similarity as set in Solr * when scoring Querqy boost queries but fake the document frequency so that all term queries under a given * {@link org.apache.lucene.search.DisjunctionMaxQuery} us the same document frequency. This avoids situations * in which the rarer of two synonymous terms would get a higher score than the more common term. It also fixes * the IDF problem for the same term value occurring in two or more different fields with different frequencies. * */ public static final String QBOOST_SIMILARITY_SCORE_DFC = "dfc"; public static final String QBOOST_SIMILARITY_SCORE_DEFAULT = QBOOST_SIMILARITY_SCORE_DFC; /** * This parameter controls whether field boosting should be applied to Querqy boost queries. */ public static final String QBOOST_FIELD_BOOST = "qboost.fieldBoost"; /** * A possible value of QBOOST_FIELD_BOOST: Do not apply any field boosting to Querqy boost queries. */ public static final String QBOOST_FIELD_BOOST_OFF = "off"; /** * A possible value of QBOOST_FIELD_BOOST: Use the field boosting as set by parameter {@link #FBM} for Querqy boost * queries. */ public static final String QBOOST_FIELD_BOOST_ON = "on"; public static final String QBOOST_FIELD_BOOST_DEFAULT = QBOOST_FIELD_BOOST_ON; public static final float DEFAULT_GQF_VALUE = Float.MIN_VALUE; static final String MATCH_ALL = "*:*"; // the QF parsing code is copied from org.apache.solr.util.SolrPluginUtils // and // org.apache.solr.search.DisMaxQParser, replacing the default boost factor // null with 1f private static final Pattern whitespacePattern = Pattern.compile("\\s+"); private static final Pattern caratPattern = Pattern.compile("\\^"); final Analyzer queryAnalyzer; final RewriteChain rewriteChain; final QuerqyParser querqyParser; final Map<String, Float> userQueryFields; final Map<String, Float> generatedQueryFields; final LuceneQueryBuilder builder; final DocumentFrequencyCorrection dfc; final DocumentFrequencyAndTermContextProvider boostDftcp; final SearchFieldsAndBoosting boostSearchFieldsAndBoostings; protected List<Query> filterQueries = null; protected Map<String, Object> context = null; protected PublicExtendedDismaxConfiguration config = null; protected List<Query> boostQueries = null; protected final boolean useReRankForBoostQueries; protected final int reRankNumDocs; protected final TermQueryCache termQueryCache; protected final float qpfTie; protected final boolean debugQuery; protected Query userQuery = null; public QuerqyDismaxQParser(String qstr, SolrParams localParams, SolrParams params, SolrQueryRequest req, RewriteChain rewriteChain, QuerqyParser querqyParser, TermQueryCache termQueryCache) throws SyntaxError { super(qstr, localParams, params, req); this.querqyParser = querqyParser; this.termQueryCache = termQueryCache; if (config == null) { // this is a hack that works around ExtendedDismaxQParser keeping the // config member var private // and avoids calling createConfiguration() twice config = createConfiguration(qstr, localParams, params, req); } IndexSchema schema = req.getSchema(); queryAnalyzer = schema.getQueryAnalyzer(); this.rewriteChain = rewriteChain; SolrParams solrParams = SolrParams.wrapDefaults(localParams, params); userQueryFields = parseQueryFields(req.getSchema(), solrParams, DisMaxParams.QF, 1f, true); generatedQueryFields = parseQueryFields(req.getSchema(), solrParams, GQF, null, false); if (generatedQueryFields.isEmpty()) { for (Map.Entry<String, Float> entry: userQueryFields.entrySet()) { generatedQueryFields.put(entry.getKey(), entry.getValue() * config.generatedFieldBoostFactor); } } else { for (Map.Entry<String, Float> entry: generatedQueryFields.entrySet()) { if (entry.getValue() == null) { String name = entry.getKey(); Float nonGeneratedBoostFactor = userQueryFields.get(name); if (nonGeneratedBoostFactor == null) { nonGeneratedBoostFactor = 1f; } entry.setValue(nonGeneratedBoostFactor * config.generatedFieldBoostFactor); } } } dfc = new DocumentFrequencyCorrection(); useReRankForBoostQueries = QBOOST_METHOD_RERANK.equals(solrParams.get(QBOOST_METHOD, QBOOST_METHOD_DEFAULT)); if (useReRankForBoostQueries) { reRankNumDocs = solrParams.getInt(QBOOST_RERANK_NUMDOCS, DEFAULT_RERANK_NUMDOCS); } else { reRankNumDocs = 0; } final SearchFieldsAndBoosting searchFieldsAndBoosting = new SearchFieldsAndBoosting(getFieldBoostModelFromParam(solrParams), userQueryFields, generatedQueryFields, config.generatedFieldBoostFactor); builder = new LuceneQueryBuilder(dfc, queryAnalyzer, searchFieldsAndBoosting, config.getTieBreaker(), termQueryCache); final String boostSimScore = solrParams.get(QBOOST_SIMILARITY_SCORE, QBOOST_SIMILARITY_SCORE_DEFAULT); if (QBOOST_SIMILARITY_SCORE_DFC.equals(boostSimScore)) { boostDftcp = dfc; } else if (QBOOST_SIMILARITY_SCORE_ON.equals(boostSimScore)) { boostDftcp = new StandardDocumentFrequencyAndTermContextProvider(); } else if (QBOOST_SIMILARITY_SCORE_OFF.equals(boostSimScore)) { boostDftcp = null; } else { throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Unknown similarity score handling for Querqy boost queries: " + boostSimScore); } final String boostFieldBoost = solrParams.get(QBOOST_FIELD_BOOST, QBOOST_FIELD_BOOST_DEFAULT); if (boostFieldBoost.equals(QBOOST_FIELD_BOOST_ON)) { boostSearchFieldsAndBoostings = searchFieldsAndBoosting; } else if (boostFieldBoost.equals(QBOOST_FIELD_BOOST_OFF)) { boostSearchFieldsAndBoostings = searchFieldsAndBoosting.withFieldBoostModel(FieldBoostModel.NONE); } else { throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Unknown field boost model for Querqy boost queries: " + boostFieldBoost); } qpfTie = solrParams.getFloat(QPF_TIE, config.getTieBreaker()); debugQuery = req.getParams().getBool(CommonParams.DEBUG_QUERY, false); } protected FieldBoostModel getFieldBoostModelFromParam(SolrParams solrParams) { String fbm = solrParams.get(FBM, FBM_DEFAULT); if (fbm.equals(FBM_FIXED)) { return FieldBoostModel.FIXED; } else if (fbm.equals(FBM_PRMS)) { return FieldBoostModel.PRMS; } throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Unknown field boost model: " + fbm); } @Override protected PublicExtendedDismaxConfiguration createConfiguration(String qstr, SolrParams localParams, SolrParams params, SolrQueryRequest req) { // this is a hack that works around ExtendedDismaxQParser keeping the // config member var private // and avoids calling createConfiguration() twice this.config = new PublicExtendedDismaxConfiguration(localParams, params, req);// super.createConfiguration(qstr, // localParams, // params, // req); return config; } /* * (non-Javadoc) * * @see org.apache.solr.search.QParser#parse() */ @Override public Query parse() throws SyntaxError { // TODO q.alt String userQueryString = getString(); if (userQueryString == null) { throw new SyntaxError("query string is null"); } userQueryString = userQueryString.trim(); if (userQueryString.length() == 0) { throw new SyntaxError("query string is empty"); } Query mainQuery; ExpandedQuery expandedQuery = null; List<Query> querqyBoostQueries = null; final Query phraseFieldQuery; if ((userQueryString.charAt(0) == '*') && (userQueryString.length() == 1 || MATCH_ALL.equals(userQueryString))) { mainQuery = new MatchAllDocsQuery(); dfc.finishedUserQuery(); phraseFieldQuery = null; } else { expandedQuery = makeExpandedQuery(); phraseFieldQuery = makePhraseFieldQueries(expandedQuery.getUserQuery()); context = new HashMap<>(); if (debugQuery) { context.put(ContextAwareQueryRewriter.CONTEXT_KEY_DEBUG_ENABLED, true); } expandedQuery = rewriteChain.rewrite(expandedQuery, context); mainQuery = makeUserQuery(expandedQuery); dfc.finishedUserQuery(); applyFilterQueries(expandedQuery); querqyBoostQueries = getQuerqyBoostQueries(expandedQuery); } boostQueries = getBoostQueries(); List<Query> boostFunctions = getBoostFunctions(); List<ValueSource> multiplicativeBoosts = getMultiplicativeBoosts(); boolean hasMultiplicativeBoosts = multiplicativeBoosts != null && !multiplicativeBoosts.isEmpty(); boolean hasQuerqyBoostQueries = querqyBoostQueries != null && !querqyBoostQueries.isEmpty(); // do we have to add a boost query as an optional clause to the main query? boolean hasOptBoost = (boostQueries != null && !boostQueries.isEmpty()) || (boostFunctions != null && !boostFunctions.isEmpty()) || (phraseFieldQuery != null) || hasMultiplicativeBoosts || (hasQuerqyBoostQueries && !useReRankForBoostQueries); if (hasOptBoost) { BooleanQuery.Builder builder = new BooleanQuery.Builder(); builder.setDisableCoord(true); builder.add(mainQuery, Occur.MUST); if (boostQueries != null) { for (Query f : boostQueries) { builder.add(f, BooleanClause.Occur.SHOULD); } } if (boostFunctions != null) { for (Query f : boostFunctions) { builder.add(f, BooleanClause.Occur.SHOULD); } } if (phraseFieldQuery != null) { builder.add(phraseFieldQuery, BooleanClause.Occur.SHOULD); } if (hasQuerqyBoostQueries && !useReRankForBoostQueries) { for (Query q : querqyBoostQueries) { builder.add(q, BooleanClause.Occur.SHOULD); } } BooleanQuery bq = builder.build(); if (hasMultiplicativeBoosts) { if (multiplicativeBoosts.size() > 1) { ValueSource prod = new ProductFloatFunction( multiplicativeBoosts.toArray(new ValueSource[multiplicativeBoosts.size()])); mainQuery = new BoostedQuery(bq, prod); } else { mainQuery = new BoostedQuery(bq, multiplicativeBoosts.get(0)); } } else { mainQuery = bq; } } if (useReRankForBoostQueries && hasQuerqyBoostQueries) { final BooleanQuery.Builder builder = new BooleanQuery.Builder(); builder.setDisableCoord(true); for (final Query q : querqyBoostQueries) { builder.add(q, BooleanClause.Occur.SHOULD); } mainQuery = new QuerqyReRankQuery(mainQuery, builder.build(), reRankNumDocs, 1.0); } return mainQuery; } @Override public Query getHighlightQuery() throws SyntaxError { if (userQuery == null) { parse(); } return userQuery; } protected List<Query> getQuerqyBoostQueries(final ExpandedQuery expandedQuery) throws SyntaxError { List<Query> result = transformBoostQueries(expandedQuery.getBoostUpQueries(), 1f); List<Query> down = transformBoostQueries(expandedQuery.getBoostDownQueries(), -1f); if (down != null) { if (result == null) { result = down; } else { result.addAll(down); } } return result; } public List<Query> transformBoostQueries(final Collection<BoostQuery> boostQueries, final float factor) throws SyntaxError { final List<Query> result; if (boostQueries != null && !boostQueries.isEmpty()) { result = new LinkedList<>(); for (BoostQuery bq : boostQueries) { Query luceneQuery = null; QuerqyQuery<?> boostQuery = bq.getQuery(); if (boostQuery instanceof RawQuery) { QParser bqp = QParser.getParser(((RawQuery) boostQuery).getQueryString(), null, req); luceneQuery = bqp.getQuery(); } else if (boostQuery instanceof querqy.model.Query) { LuceneQueryBuilder luceneQueryBuilder = new LuceneQueryBuilder(boostDftcp, queryAnalyzer, boostSearchFieldsAndBoostings, config.getTieBreaker(), termQueryCache); try { luceneQuery = luceneQueryBuilder.createQuery((querqy.model.Query) boostQuery, factor < 0f); if (luceneQuery != null) { luceneQuery = wrapQuery(luceneQuery); } } catch (IOException e) { throw new RuntimeException(e); } } if (luceneQuery != null) { final float boost = bq.getBoost() * factor; if (boost != 1f) { result.add(new org.apache.lucene.search.BoostQuery(luceneQuery, boost)); } else { result.add(luceneQuery); } } } } else { result = null; } return result; } public ExpandedQuery makeExpandedQuery() { return new ExpandedQuery(querqyParser.parse(qstr)); } public Query makePhraseFieldQueries(final querqy.model.Query userQuery) { if (userQuery != null) { final List<querqy.model.BooleanClause> clauses = userQuery.getClauses(); if (clauses.size() > 1) { final List<FieldParams> allPhraseFields = new LinkedList<>(); final IndexSchema schema = req.getSchema(); for (final FieldParams field : config.getAllPhraseFields()) { if (isFieldPhraseQueryable(schema.getFieldOrNull(field.getField()))) { allPhraseFields.add(field); } } if (allPhraseFields != null && !allPhraseFields.isEmpty()) { final List<String> sequence = new LinkedList<>(); for (final querqy.model.BooleanClause clause : clauses) { if (clause instanceof DisjunctionMaxQuery) { final DisjunctionMaxQuery dmq = (DisjunctionMaxQuery) clause; if (dmq.occur != querqy.model.SubQuery.Occur.MUST_NOT) { for (final DisjunctionMaxClause dmqClause : dmq.getClauses()) { if (dmqClause instanceof Term) { final ComparableCharSequence value = ((Term) dmqClause).getValue(); final int length = value.length(); final StringBuilder sb = new StringBuilder(length); for (int i = 0; i < length; i++) { sb.append(value.charAt(i)); } sequence.add(sb.toString()); break; } } } } } if (sequence.size() > 1) { final List<Query> disjuncts = new LinkedList<>(); final QueryBuilder queryBuilder = new QueryBuilder(schema.getQueryAnalyzer()); final List<String>[] shingles = new List[4]; String pf = null; for (final FieldParams fieldParams : allPhraseFields) { final int n = fieldParams.getWordGrams(); final int slop = fieldParams.getSlop(); final String fieldname = fieldParams.getField(); if (n == 0) { if (pf == null) { final StringBuilder sb = new StringBuilder(getString().length()); for (final String term : sequence) { if (sb.length() > 0) { sb.append(' '); } sb.append(term); } pf = sb.toString(); } final Query pq = queryBuilder.createPhraseQuery(fieldname, pf, slop); if (pq != null) { disjuncts.add(LuceneQueryUtil.boost(pq, fieldParams.getBoost())); } } else if (n <= sequence.size()) { if (shingles[n] == null) { shingles[n] = new LinkedList<>(); for (int i = 0, lenI = sequence.size() - n + 1; i < lenI; i++) { final StringBuilder sb = new StringBuilder(); for (int j = i, lenJ = j + n; j < lenJ; j++) { if (sb.length() > 0) { sb.append(' '); } sb.append(sequence.get(j)); } shingles[n].add(sb.toString()); } } final List<Query> nGramQueries = new ArrayList<>(shingles[n].size()); for (final String nGram : shingles[n]) { final Query pq = queryBuilder.createPhraseQuery(fieldname, nGram, slop); if (pq != null) { nGramQueries.add(pq); } } switch (nGramQueries.size()) { case 0: break; case 1: { final Query nGramQuery = nGramQueries.get(0); disjuncts.add(LuceneQueryUtil.boost(nGramQuery, fieldParams.getBoost())); break; } default: final BooleanQuery.Builder builder = new BooleanQuery.Builder(); builder.setDisableCoord(true); for (final Query nGramQuery : nGramQueries) { builder.add(nGramQuery, Occur.SHOULD); } final BooleanQuery bq = builder.build(); disjuncts.add(LuceneQueryUtil.boost(bq, fieldParams.getBoost())); } } } switch (disjuncts.size()) { case 0: break; case 1: return disjuncts.get(0); default : return new org.apache.lucene.search.DisjunctionMaxQuery(disjuncts, qpfTie); } } } } } return null; } public boolean isFieldPhraseQueryable(final SchemaField field) { if (field != null) { final FieldType fieldType = field.getType(); if ((fieldType instanceof TextField) && !field.omitPositions() && !field.omitTermFreqAndPositions()) { return true; } } return false; } /** * @param query */ public Query applyMinShouldMatch(final Query query) { if (!(query instanceof BooleanQuery)) { return query; } final BooleanQuery bq = (BooleanQuery) query; List<BooleanClause> clauses = bq.clauses(); if (clauses.size() < 2) { return bq; } for (BooleanClause clause : clauses) { if ((clause.getQuery() instanceof BooleanQuery) && (clause.getOccur() != Occur.MUST)) { return bq; // seems to be a complex query with sub queries - do not // apply mm } } return SolrPluginUtils.setMinShouldMatch(bq, config.getMinShouldMatch()); } public void applyFilterQueries(final ExpandedQuery expandedQuery) throws SyntaxError { Collection<QuerqyQuery<?>> filterQueries = expandedQuery.getFilterQueries(); if (filterQueries != null && !filterQueries.isEmpty()) { List<Query> fqs = new LinkedList<>(); for (QuerqyQuery<?> qfq : filterQueries) { if (qfq instanceof RawQuery) { QParser fqParser = QParser.getParser(((RawQuery) qfq).getQueryString(), null, req); fqs.add(fqParser.getQuery()); } else if (qfq instanceof querqy.model.Query) { builder.reset(); try { fqs.add(builder.createQuery((querqy.model.Query) qfq)); } catch (IOException e) { throw new RuntimeException(e); } } } this.filterQueries = fqs; } } public Query makeUserQuery(final ExpandedQuery expandedQuery) { builder.reset(); try { userQuery = wrapQuery( applyMinShouldMatch( builder.createQuery(expandedQuery.getUserQuery()))); return userQuery; } catch (IOException e) { throw new RuntimeException(e); } } public Query wrapQuery(final Query query) { if (query == null || dfc == null) { return query; } else { final WrappedQuery wrappedQuery = new WrappedQuery(query); wrappedQuery.setCache(true); wrappedQuery.setCacheSep(false); return wrappedQuery; } } public List<Query> getFilterQueries() { return filterQueries; } public Map<String, Object> getContext() { return context; } /** * Copied from DisMaxQParser * * @param solrParams * @return * @throws SyntaxError */ public Query getAlternateUserQuery(final SolrParams solrParams) throws SyntaxError { String altQ = solrParams.get(DisMaxParams.ALTQ); if (altQ != null) { QParser altQParser = subQuery(altQ, null); return altQParser.getQuery(); } else { return null; } } /** * Given a string containing fieldNames and boost info, converts it to a Map * from field name to boost info. * * <p> * Doesn't care if boost info is negative, you're on your own. * </p> * <p> * Doesn't care if boost info is missing, again: you're on your own. * </p> * * @param in * a String like "fieldOne^2.3 fieldTwo fieldThree^-0.4" * @return Map of fieldOne => 2.3, fieldTwo => null, fieldThree => * -0.4 */ public static Map<String, Float> parseFieldBoosts(final String in, final Float defaultBoost) { return parseFieldBoosts(new String[] { in }, defaultBoost); } /** * Like <code>parseFieldBoosts(String)</code>, but parses all the strings in * the provided array (which may be null). * * @param fieldLists * an array of Strings eg. * <code>{"fieldOne^2.3", "fieldTwo", fieldThree^-0.4}</code> * @return Map of fieldOne => 2.3, fieldTwo => null, fieldThree => * -0.4 */ public static Map<String, Float> parseFieldBoosts(final String[] fieldLists, final Float defaultBoost) { if (null == fieldLists || 0 == fieldLists.length) { return new HashMap<>(); } final Map<String, Float> out = new HashMap<>(7); for (String in : fieldLists) { if (null == in) { continue; } in = in.trim(); if (in.length() == 0) { continue; } final String[] bb = whitespacePattern.split(in); for (final String s : bb) { final String[] bbb = caratPattern.split(s); out.put(bbb[0], 1 == bbb.length ? defaultBoost : Float.valueOf(bbb[1])); } } return out; } /** * Copied from DisMxQParser (as we don't handle user fields/aliases yet) * * Uses {@link SolrPluginUtils#parseFieldBoosts(String)} with the 'qf' * parameter. Falls back to the 'df' parameter or * {@link org.apache.solr.schema.IndexSchema#getDefaultSearchFieldName()}. */ public static Map<String, Float> parseQueryFields(final IndexSchema indexSchema, final SolrParams solrParams, final String fieldName, final Float defaultBoost, final boolean useDfFallback) throws SyntaxError { final Map<String, Float> queryFields = parseFieldBoosts(solrParams.getParams(fieldName), defaultBoost); if (queryFields.isEmpty() && useDfFallback) { final String df = QueryParsing.getDefaultField(indexSchema, solrParams.get(CommonParams.DF)); if (df == null) { throw new SyntaxError("Neither " + fieldName + ", " + CommonParams.DF + ", nor the default search field are present."); } queryFields.put(df, defaultBoost); } return queryFields; } public class PublicExtendedDismaxConfiguration extends ExtendedDismaxConfiguration { final float generatedFieldBoostFactor; /** * @param localParams * @param params * @param req */ public PublicExtendedDismaxConfiguration(final SolrParams localParams, final SolrParams params, final SolrQueryRequest req) { super(localParams, params, req); generatedFieldBoostFactor = solrParams.getFloat(GFB, 1f); } public float getTieBreaker() { return tiebreaker; } public String getMinShouldMatch() { return minShouldMatch; } } }