All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.solr.search.RankQParserPlugin Maven / Gradle / Ivy

There is a newer version: 9.7.0
Show newest version
/*
 * 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 java.util.Locale;
import java.util.Objects;
import org.apache.lucene.document.FeatureField;
import org.apache.lucene.search.Query;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.schema.RankField;
import org.apache.solr.schema.SchemaField;

/**
 * {@code RankQParserPlugin} can be used to introduce document-depending scoring factors to ranking.
 * While this {@code QParser} delivers a (subset of) functionality already available via {@link
 * FunctionQParser}, the benefit is that {@code RankQParserPlugin} can be used in combination with
 * the {@code minExactCount} to use BlockMax-WAND algorithm (skip non-competitive documents) to
 * provide faster responses.
 *
 * @see RankField
 * @lucene.experimental
 * @since 8.6
 */
public class RankQParserPlugin extends QParserPlugin {

  public static final String NAME = "rank";
  public static final String FIELD = "f";
  public static final String FUNCTION = "function";
  public static final String WEIGHT = "weight";
  public static final String PIVOT = "pivot";
  public static final String SCALING_FACTOR = "scalingFactor";
  public static final String EXPONENT = "exponent";

  private static final FeatureFieldFunction DEFAULT_FUNCTION = FeatureFieldFunction.SATU;

  private enum FeatureFieldFunction {
    SATU {
      @Override
      public Query createQuery(String fieldName, SolrParams params) throws SyntaxError {
        Float weight = params.getFloat(WEIGHT);
        Float pivot = params.getFloat(PIVOT);
        if (pivot == null && (weight == null || Float.compare(weight.floatValue(), 1f) == 0)) {
          // No IAE expected in this case
          return FeatureField.newSaturationQuery(RankField.INTERNAL_RANK_FIELD_NAME, fieldName);
        }
        if (pivot == null) {
          throw new SyntaxError(
              "A pivot value needs to be provided if the weight is not 1 on \"satu\" function");
        }
        if (weight == null) {
          weight = Float.valueOf(1);
        }
        try {
          return FeatureField.newSaturationQuery(
              RankField.INTERNAL_RANK_FIELD_NAME, fieldName, weight, pivot);
        } catch (IllegalArgumentException iae) {
          throw new SyntaxError(iae.getMessage());
        }
      }
    },
    LOG {
      @Override
      public Query createQuery(String fieldName, SolrParams params) throws SyntaxError {
        float weight = params.getFloat(WEIGHT, 1f);
        float scalingFactor = params.getFloat(SCALING_FACTOR, 1f);
        try {
          return FeatureField.newLogQuery(
              RankField.INTERNAL_RANK_FIELD_NAME, fieldName, weight, scalingFactor);
        } catch (IllegalArgumentException iae) {
          throw new SyntaxError(iae.getMessage());
        }
      }
    },
    SIGM {
      @Override
      public Query createQuery(String fieldName, SolrParams params) throws SyntaxError {
        float weight = params.getFloat(WEIGHT, 1f);
        Float pivot = params.getFloat(PIVOT);
        if (pivot == null) {
          throw new SyntaxError("A pivot value needs to be provided when using \"sigm\" function");
        }
        Float exponent = params.getFloat(EXPONENT);
        if (exponent == null) {
          throw new SyntaxError(
              "An exponent value needs to be provided when using \"sigm\" function");
        }
        try {
          return FeatureField.newSigmoidQuery(
              RankField.INTERNAL_RANK_FIELD_NAME, fieldName, weight, pivot, exponent);
        } catch (IllegalArgumentException iae) {
          throw new SyntaxError(iae.getMessage());
        }
      }
    };

    public abstract Query createQuery(String fieldName, SolrParams params) throws SyntaxError;
  }

  @Override
  public QParser createParser(
      String qstr, SolrParams localParams, SolrParams params, SolrQueryRequest req) {
    Objects.requireNonNull(localParams, "LocalParams String can't be null");
    Objects.requireNonNull(req, "SolrQueryRequest can't be null");
    return new RankQParser(qstr, localParams, params, req);
  }

  public static class RankQParser extends QParser {

    private final String field;

    public RankQParser(
        String qstr, SolrParams localParams, SolrParams params, SolrQueryRequest req) {
      super(qstr, localParams, params, req);
      this.field = localParams.get(FIELD);
    }

    @Override
    public Query parse() throws SyntaxError {
      if (this.field == null || this.field.isEmpty()) {
        throw new SyntaxError("Field can't be empty in rank queries");
      }
      SchemaField schemaField = req.getSchema().getFieldOrNull(field);
      if (schemaField == null) {
        throw new SyntaxError("Field \"" + this.field + "\" not found");
      }
      if (!(schemaField.getType() instanceof RankField)) {
        throw new SyntaxError("Field \"" + this.field + "\" is not a RankField");
      }
      return getFeatureFieldFunction(localParams.get(FUNCTION)).createQuery(field, localParams);
    }

    private FeatureFieldFunction getFeatureFieldFunction(String function) throws SyntaxError {
      FeatureFieldFunction f = null;
      if (function == null || function.isEmpty()) {
        f = DEFAULT_FUNCTION;
      } else {
        try {
          f = FeatureFieldFunction.valueOf(function.toUpperCase(Locale.ROOT));
        } catch (IllegalArgumentException iae) {
          throw new SyntaxError("Unknown function in rank query: \"" + function + "\"");
        }
      }
      return f;
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy