module Blacklight::Solr::SearchBuilderBehavior

Public Instance Methods

add_additional_filters(solr_parameters, additional_filters = nil) click to toggle source
# File lib/blacklight/solr/search_builder_behavior.rb, line 65
def add_additional_filters(solr_parameters, additional_filters = nil)
  q = additional_filters || @additional_filters

  return if q.blank?

  if q.values.any?(&:blank?)
    # if any field parameters are empty, exclude _all_ results
    solr_parameters.append_query "{!lucene}NOT *:*"
  else
    composed_query = q.map do |field, values|
      "#{field}:(#{Array(values).map { |x| solr_param_quote(x) }.join(' OR ')})"
    end.join(" AND ")

    solr_parameters.append_query "{!lucene}#{composed_query}"
  end

  solr_parameters[:defType] = 'lucene'
  solr_parameters[:spellcheck] = 'false'
end
add_adv_search_clauses(solr_parameters) click to toggle source

Transform “clause” parameters into the Solr JSON Query DSL

# File lib/blacklight/solr/search_builder_behavior.rb, line 95
def add_adv_search_clauses(solr_parameters)
  return if search_state.clause_params.blank?

  defaults = { must: [], must_not: [], should: [] }
  default_op = blacklight_params[:op]&.to_sym || :must
  solr_parameters[:mm] = 1 if default_op == :should && search_state.clause_params.values.any? { |clause| }

  search_state.clause_params.each_value do |clause|
    op, query = adv_search_clause(clause, default_op)
    next unless defaults.key?(op)

    solr_parameters.append_boolean_query(op, query)
  end
end
add_facet_fq_to_solr(solr_parameters) click to toggle source

Add any existing facet limits, stored in app-level HTTP query as :f, to solr as appropriate :fq query.

# File lib/blacklight/solr/search_builder_behavior.rb, line 123
def add_facet_fq_to_solr(solr_parameters)
  # convert a String value into an Array
  if solr_parameters[:fq].is_a? String
    solr_parameters[:fq] = [solr_parameters[:fq]]
  end

  search_state.filters.each do |filter|
    if filter.config.filter_query_builder
      filter_query, subqueries = filter.config.filter_query_builder.call(self, filter, solr_parameters)

      Array(filter_query).each do |fq|
        solr_parameters.append_filter_query(fq)
      end
      solr_parameters.merge!(subqueries) if subqueries
    else
      filter.values.compact_blank.each do |value|
        filter_query, subqueries = if value.is_a?(Array)
                                     facet_inclusive_value_to_fq_string(filter.key, value.compact_blank)
                                   else
                                     facet_value_to_fq_string(filter.config.key, value)
                                   end

        solr_parameters.append_filter_query filter_query
        solr_parameters.merge!(subqueries) if subqueries
      end
    end
  end
end
add_facet_paging_to_solr(solr_params) click to toggle source
# File lib/blacklight/solr/search_builder_behavior.rb, line 244
def add_facet_paging_to_solr(solr_params)
  return if facet.blank?

  facet_config = blacklight_config.facet_fields[facet]

  solr_params[:rows] = 0

  limit = if solr_params["facet.limit"]
            solr_params["facet.limit"].to_i
          else
            facet_config.fetch(:more_limit, blacklight_config.default_more_limit)
          end

  page = search_state.facet_page
  sort = search_state.facet_sort
  prefix = search_state.facet_prefix
  offset = (page - 1) * limit

  if facet_config.json
    add_solr_facet_json_params(solr_parameters, facet, facet_config, limit: limit + 1, offset: offset, sort: sort, prefix: prefix)
    return
  end

  # Now override with our specific things for fetching facet values
  facet_ex = facet_config.respond_to?(:ex) ? facet_config.ex : nil
  solr_params[:'facet.field'] = with_ex_local_param(facet_ex, facet_config.field)

  # Need to set as f.facet_field.facet.* to make sure we
  # override any field-specific default in the solr request handler.
  solr_params[:"f.#{facet_config.field}.facet.limit"] = limit + 1
  solr_params[:"f.#{facet_config.field}.facet.offset"] = offset
  solr_params[:"f.#{facet_config.field}.facet.sort"] = sort if sort
  solr_params[:"f.#{facet_config.field}.facet.prefix"] = prefix if prefix
end
add_facetting_to_solr(solr_parameters) click to toggle source

Add appropriate Solr facetting directives in, including taking account of our facet paging/‘more’. This is not about solr ‘fq’, this is about solr facet.* params.

# File lib/blacklight/solr/search_builder_behavior.rb, line 171
def add_facetting_to_solr(solr_parameters)
  facet_fields_to_include_in_request.each do |field_name, facet|
    solr_parameters[:facet] ||= true

    if facet.json
      add_solr_facet_json_params(solr_parameters, field_name, facet)
      next
    end

    if facet.pivot
      solr_parameters.append_facet_pivot with_ex_local_param(facet.ex, facet.pivot.join(","))
    elsif facet.query
      solr_parameters.append_facet_query facet.query.values.map { |x| with_ex_local_param(facet.ex, x[:fq]) }
    else
      solr_parameters.append_facet_fields with_ex_local_param(facet.ex, facet.field)
    end

    if facet.sort
      solr_parameters[:"f.#{facet.field}.facet.sort"] = facet.sort
    end

    if facet.solr_params
      facet.solr_params.each do |k, v|
        solr_parameters[:"f.#{facet.field}.#{k}"] = v
      end
    end

    limit = facet_limit_with_pagination(field_name)
    solr_parameters[:"f.#{facet.field}.facet.limit"] = limit if limit
  end
end
add_group_config_to_solr(solr_parameters) click to toggle source

Remove the group parameter if we’ve faceted on the group field (e.g. for the full results for a group)

# File lib/blacklight/solr/search_builder_behavior.rb, line 240
def add_group_config_to_solr solr_parameters
  solr_parameters[:group] = false if search_state.filter(grouped_key_for_results).any?
end
add_paging_to_solr(solr_params) click to toggle source

copy paging params from BL app over to solr, changing app level per_page and page to Solr rows and start.

# File lib/blacklight/solr/search_builder_behavior.rb, line 225
def add_paging_to_solr(solr_params)
  rows(solr_params[:rows] || 10) if rows.nil?

  solr_params[:rows] = rows

  solr_params[:start] = start if start.nonzero?
end
add_query_to_solr(solr_parameters) click to toggle source

Take the user-entered query, and put it in the solr params, including config’s “search field” params for current search field. also include setting spellcheck.q.

# File lib/blacklight/solr/search_builder_behavior.rb, line 48
def add_query_to_solr(solr_parameters)
  ##
  # Create Solr 'q' including the user-entered q, prefixed by any
  # solr LocalParams in config, using solr LocalParams syntax.
  # http://wiki.apache.org/solr/LocalParams
  ##
  if search_field&.query_builder.present?
    add_search_field_query_builder_params(solr_parameters)
  elsif search_field&.clause_params.present?
    add_search_field_with_json_query_parameters(solr_parameters)
  elsif search_field&.solr_local_parameters.present?
    add_search_field_with_local_parameters(solr_parameters)
  elsif !search_state&.query_param.is_a?(Hash)
    solr_parameters.append_query search_state.query_param
  end
end
add_search_field_default_parameters(solr_parameters) click to toggle source
# File lib/blacklight/solr/search_builder_behavior.rb, line 33
def add_search_field_default_parameters(solr_parameters)
  ###
  # Merge in search field configured values, if present, over-writing general
  # defaults
  if search_field
    solr_parameters[:qt] = search_field.qt if search_field.qt

    solr_parameters.deep_merge!(search_field.solr_parameters) if search_field.solr_parameters
  end
end
add_search_field_with_json_query_parameters(solr_parameters) click to toggle source
# File lib/blacklight/solr/search_builder_behavior.rb, line 85
def add_search_field_with_json_query_parameters(solr_parameters)
  return unless search_state.query_param

  bool_query = search_field.clause_params.transform_values { |v| v.merge(query: search_state.query_param) }
  solr_parameters["spellcheck.q"] ||= search_state.query_param

  solr_parameters.append_boolean_query(:must, bool_query)
end
add_solr_facet_json_params(solr_parameters, field_name, facet, **additional_parameters) click to toggle source
# File lib/blacklight/solr/search_builder_behavior.rb, line 152
def add_solr_facet_json_params(solr_parameters, field_name, facet, **additional_parameters)
  solr_parameters[:json] ||= { facet: {} }
  solr_parameters[:json][:facet] ||= {}

  field_config = facet.json.respond_to?(:reverse_merge) ? facet.json : {}

  field_config = field_config.reverse_merge(
    type: 'terms',
    field: facet.field,
    limit: facet_limit_with_pagination(field_name)
  ).merge(additional_parameters)

  solr_parameters[:json][:facet][field_name] = field_config.compact_blank
end
add_solr_fields_to_query(solr_parameters) click to toggle source
# File lib/blacklight/solr/search_builder_behavior.rb, line 203
def add_solr_fields_to_query solr_parameters
  blacklight_config.show_fields.select(&method(:should_add_field_to_request?)).each_value do |field|
    field.solr_params.each do |k, v|
      solr_parameters[:"f.#{field.field}.#{k}"] = v
    end if field.solr_params
  end

  blacklight_config.index_fields.select(&method(:should_add_field_to_request?)).each_value do |field|
    if field.highlight
      solr_parameters[:hl] = true
      solr_parameters.append_highlight_field field.field
    end

    field.solr_params.each do |k, v|
      solr_parameters[:"f.#{field.field}.#{k}"] = v
    end if field.solr_params
  end
end
add_sorting_to_solr(solr_parameters) click to toggle source

copy sorting params from BL app over to solr

# File lib/blacklight/solr/search_builder_behavior.rb, line 235
def add_sorting_to_solr(solr_parameters)
  solr_parameters[:sort] = sort if sort.present?
end
adv_search_clause(clause, default_op) click to toggle source

@return [Array] the first element is the query operator and the second is the value to add

# File lib/blacklight/solr/search_builder_behavior.rb, line 111
def adv_search_clause(clause, default_op)
  op = clause[:op]&.to_sym || default_op
  field = (blacklight_config.search_fields || {})[clause[:field]] if clause[:field]

  return unless field&.clause_params && clause[:query].present?

  [op, field.clause_params.transform_values { |v| v.merge(query: clause[:query]) }]
end
default_solr_parameters(solr_parameters) click to toggle source

Start with general defaults from BL config. Need to use custom merge to dup values, to avoid later mutating the original by mistake.

# File lib/blacklight/solr/search_builder_behavior.rb, line 21
def default_solr_parameters(solr_parameters)
  blacklight_config.default_solr_params.each do |key, value|
    solr_parameters[key] ||= if value.respond_to? :deep_dup
                               value.deep_dup
                             elsif value.respond_to?(:dup) && value.duplicable?
                               value.dup
                             else
                               value
                             end
  end
end
facet_limit_for(facet_field) click to toggle source

Look up facet limit for given facet_field. Will look at config, and if config is ‘true’ will look up from Solr @response if available. If no limit is avaialble, returns nil. Used from add_facetting_to_solr to supply f.fieldname.facet.limit values in solr request (no @response available), and used in display (with @response available) to create a facet paginator with the right limit.

# File lib/blacklight/solr/search_builder_behavior.rb, line 293
def facet_limit_for(facet_field)
  facet = blacklight_config.facet_fields[facet_field]
  return if facet.blank?

  if facet.limit
    facet.limit == true ? blacklight_config.default_facet_limit : facet.limit
  end
end
facet_limit_with_pagination(field_name) click to toggle source

Support facet paging and ‘more’ links, by sending a facet.limit one more than what we want to page at, according to configured facet limits.

# File lib/blacklight/solr/search_builder_behavior.rb, line 305
def facet_limit_with_pagination(field_name)
  limit = facet_limit_for(field_name)

  return if limit.nil?

  if limit > 0
    limit + 1
  else
    limit
  end
end
solr_param_quote(val, options = {}) click to toggle source

A helper method used for generating solr LocalParams, put quotes around the term unless it’s a bare-word. Escape internal quotes if needed.

# File lib/blacklight/solr/search_builder_behavior.rb, line 321
def solr_param_quote(val, options = {})
  val = val.to_s
  options[:quote] ||= '"'
  unless val =~ /^[a-zA-Z0-9$_\-\^]+$/
    val = options[:quote] +
          # Yes, we need crazy escaping here, to deal with regexp esc too!
          val.gsub("'", "\\\\'").gsub('"', "\\\\\"") +
          options[:quote]
  end
  val
end
with_ex_local_param(ex, value) click to toggle source
# File lib/blacklight/solr/search_builder_behavior.rb, line 279
def with_ex_local_param(ex, value)
  if ex
    "{!ex=#{ex}}#{value}"
  else
    value
  end
end

Private Instance Methods

add_search_field_query_builder_params(solr_parameters) click to toggle source
# File lib/blacklight/solr/search_builder_behavior.rb, line 415
def add_search_field_query_builder_params(solr_parameters)
  q, additional_parameters = search_field.query_builder.call(self, search_field, solr_parameters)

  solr_parameters.append_query q
  solr_parameters.merge!(additional_parameters) if additional_parameters
end
add_search_field_with_local_parameters(solr_parameters) click to toggle source
# File lib/blacklight/solr/search_builder_behavior.rb, line 422
def add_search_field_with_local_parameters(solr_parameters)
  local_params = search_field.solr_local_parameters.map do |key, val|
    "#{key}=#{solr_param_quote(val, quote: "'")}"
  end.join(" ")
  solr_parameters.append_query "{!#{local_params}}#{search_state.query_param}"

  ##
  # Set Solr spellcheck.q to be original user-entered query, without
  # our local params, otherwise it'll try and spellcheck the local
  # params!
  solr_parameters["spellcheck.q"] ||= search_state.query_param
end
convert_to_term_value(value) click to toggle source
# File lib/blacklight/solr/search_builder_behavior.rb, line 386
def convert_to_term_value(value)
  case value
  when DateTime, Time
    value.utc.strftime("%Y-%m-%dT%H:%M:%SZ")
  when Date
    value.to_time(:local).strftime("%Y-%m-%dT%H:%M:%SZ")
  else
    value.to_s
  end
end
facet_fields_to_include_in_request() click to toggle source
# File lib/blacklight/solr/search_builder_behavior.rb, line 403
def facet_fields_to_include_in_request
  blacklight_config.facet_fields.select do |_field_name, facet|
    facet.include_in_request || (facet.include_in_request.nil? && blacklight_config.add_facet_fields_to_solr_request)
  end
end
facet_inclusive_value_to_fq_string(facet_field, values) click to toggle source

rubocop:enable Metrics/PerceivedComplexity

# File lib/blacklight/solr/search_builder_behavior.rb, line 365
def facet_inclusive_value_to_fq_string(facet_field, values)
  return if values.blank?

  return facet_value_to_fq_string(facet_field, values.first) if values.length == 1

  facet_config = blacklight_config.facet_fields[facet_field]

  local_params = []
  local_params << "tag=#{facet_config.tag}" if facet_config && facet_config.tag

  solr_filters = values.each_with_object({}).with_index do |(v, h), index|
    h["f_inclusive.#{facet_field}.#{index}"] = facet_value_to_fq_string(facet_field, v, use_local_params: false)
  end

  filter_query = solr_filters.keys.map do |k|
    "{!query v=$#{k}}"
  end.join(' OR ')

  ["{!lucene#{" #{local_params.join(' ')}" unless local_params.empty?}}#{filter_query}", solr_filters]
end
facet_value_to_fq_string(facet_field, value, use_local_params: true) click to toggle source

Convert a facet/value pair into a solr fq parameter rubocop:disable Metrics/PerceivedComplexity

# File lib/blacklight/solr/search_builder_behavior.rb, line 338
def facet_value_to_fq_string(facet_field, value, use_local_params: true)
  facet_config = blacklight_config.facet_fields[facet_field]

  solr_field = facet_config.field if facet_config && !facet_config.query
  solr_field ||= facet_field

  local_params = []
  local_params << "tag=#{facet_config.tag}" if use_local_params && facet_config && facet_config.tag

  if facet_config && facet_config.query
    if facet_config.query[value]
      facet_config.query[value][:fq]
    else
      # exclude all documents if the custom facet key specified was not found
      '-*:*'
    end
  elsif value.is_a?(Range)
    prefix = "{!#{local_params.join(' ')}}" unless local_params.empty?
    "#{prefix}#{solr_field}:[#{value.first} TO #{value.last}]"
  elsif value == Blacklight::SearchState::FilterField::MISSING
    "-#{solr_field}:[* TO *]"
  else
    "{!term f=#{solr_field}#{" #{local_params.join(' ')}" unless local_params.empty?}}#{convert_to_term_value(value)}"
  end
end
grouped_key_for_results() click to toggle source

The key to use to retrieve the grouped field to display

# File lib/blacklight/solr/search_builder_behavior.rb, line 399
def grouped_key_for_results
  blacklight_config.view_config(action_name: :index).group
end
search_state() click to toggle source
Calls superclass method
# File lib/blacklight/solr/search_builder_behavior.rb, line 409
def search_state
  return super if defined?(super)

  @search_state ||= Blacklight::SearchState.new(blacklight_params, blacklight_config)
end