class LSolr

A query builder of Apache Solr standard Lucene type query for Ruby.

@example How to use. Part 1:

LSolr.build(field1: 'hoge', field2: true).to_s
#=> 'field1:hoge AND field2:true'

@example How to use. Part 2:

params = {
  field01: 'hoge',
  field02: :fuga,
  field03: 14,
  field04: 7.3,
  field05: true,
  field06: false,
  field07: Date.new(7000, 7, 1),
  field08: DateTime.new(6000, 5, 31, 6, 31, 43),
  field09: Time.new(5000, 6, 30, 12, 59, 3),
  field10: LSolr.new(:field10).fuzzy_match('foo'),
  field11: [1, 2, 3],
  field12: 1..10,
  field13: 20...40,
  field14: Date.new(3000, 1, 1)..Date.new(4000, 12, 31),
  field15: (3.0..4.0).step(0.1)
}

LSolr.build(params).to_s
#=> 'field01:hoge AND
#    field02:fuga AND
#    field03:14 AND
#    field04:7.3 AND
#    field05:true AND
#    field06:false AND
#    field07:"7000-07-01T00:00:00Z" AND
#    field08:"6000-05-31T06:31:43Z" AND
#    field09:"5000-06-30T12:59:03Z" AND
#    field10:foo~2.0 AND
#    field11:(1 2 3) AND
#    field12:[1 TO 10] AND
#    field13:[20 TO 40} AND
#    field14:[3000-01-01T00:00:00Z TO 4000-12-31T00:00:00Z] AND
#    field15:[3.0 TO 4.0]'

@example How to use. Part 3:

bool1 = LSolr.new(:bool_field).match(true)
bool2 = LSolr.new(:bool_field).match(false)
date1 = LSolr.new(:date_field1).greater_than_or_equal_to('*').less_than_or_equal_to(Time.new(2000, 6, 30, 23, 59, 59))
date2 = LSolr.new(:date_field2).greater_than(Time.new(2000, 7, 1, 0, 0, 0)).less_than(Time.new(2001, 1, 1, 0, 0, 0))

left = bool1.and(date1).and(date2).wrap
right = bool2.and(date1.or(date2).wrap).wrap

left.or(right).to_s
#=> '(bool_field:true AND date_field1:[* TO 2000-06-30T23:59:59Z] AND date_field2:{2000-07-01T00:00:00Z TO 2001-01-01T00:00:00Z})
#    OR (bool_field:false AND (date_field1:[* TO 2000-06-30T23:59:59Z] OR date_field2:{2000-07-01T00:00:00Z TO 2001-01-01T00:00:00Z}))'

@example How to use. Part 4:

%w[a b c].map { |v| LSolr.new(:field).prefix_match("#{v}*") }.reduce { |a, e| a.or(e) }.wrap.not.to_s
#=> 'NOT (field:a* OR field:b* OR field:c*)'

@example How to use. Part 5:

LSolr.build('a:1').and(b: 2).to_s
#=> 'a:1 AND b:2'

Constants

AND
ArgumentError
BOOST
CONSTANT_SCORE
DELIMITER_SPACE
FORMAT_DATE_TIME
FORMAT_INSPECT
FORMAT_MILLISECOND_FOR_DATE_TYPE
FORMAT_MILLISECOND_FOR_TIME_TYPE
FORMAT_SECOND
GREATER_THAN
GREATER_THAN_OR_EQUAL_TO
IncompleteQueryError
LESS_THAN
LESS_THAN_OR_EQUAL_TO
NOT
OR
PARENTHESIS_LEFT
PARENTHESIS_RIGHT
PROXIMITY
RANGE_FUZZY_MATCH_DISTANCE
REPLACEMENT_CHAR
RESERVED_SYMBOLS
RESERVED_WORDS
TO
TypeError
WILD_CARD

Attributes

expr_not[RW]
left_parentheses[RW]
operator[RW]
prev[RW]
right_parentheses[RW]

Public Class Methods

build(params) click to toggle source

Builds composite query and returns builder instance.

@param params [Hash{Symbol => String, Symbol, Integer, Float, true, false, Range, Date, Time, Array<String, Symbol, Integer>}, String] query terms or a raw query

@return [LSolr] a instance

@raise [LSolr::ArgumentError] if specified parameters have a not supported type value

# File lib/lsolr.rb, line 114
def build(params)
  case params
  when Hash then params.map { |f, v| build_query(f, v) }.reduce { |a, e| a.and(e) }
  when String then build_raw_query(params)
  else raise TypeError, "Could not build solr query. Please specify a Hash or String value. `#{params}` given."
  end
rescue TypeError => e
  raise ArgumentError, "#{e.message} It is not a supported type."
end
new(field_name = nil) click to toggle source

Create a new query builder instance.

@param field_name [String, Symbol] a field name @return [LSolr] a instance

# File lib/lsolr.rb, line 165
def initialize(field_name = nil)
  if field_name.nil?
    @field = ''
  else
    field(field_name)
  end

  @expr_not = @value = @range_first = @range_last = @boost = @constant_score = @raw = ''
  @left_parentheses = []
  @right_parentheses = []
end

Private Class Methods

build_array_query(field, values) click to toggle source
# File lib/lsolr.rb, line 138
def build_array_query(field, values)
  values.empty? ? new(field) : new(field).match_in(values)
end
build_enumerator_query(field, values) click to toggle source
# File lib/lsolr.rb, line 150
def build_enumerator_query(field, values)
  last = nil
  values.each { |v| last = v }
  new(field).greater_than_or_equal_to(values.first).less_than_or_equal_to(last)
end
build_query(field, value) click to toggle source
# File lib/lsolr.rb, line 126
def build_query(field, value) # rubocop:disable Metrics/CyclomaticComplexity
  case value
  when String, Symbol, Integer, Float, true, false then new(field).match(value)
  when Date, Time then new(field).date_time_match(value)
  when LSolr then value
  when Array then build_array_query(field, value)
  when Range then build_range_query(field, value)
  when Enumerator then build_enumerator_query(field, value)
  else raise TypeError, "Could not build solr query. field: `#{field}`, value: `#{value}` given."
  end
end
build_range_query(field, value) click to toggle source
# File lib/lsolr.rb, line 142
def build_range_query(field, value)
  if value.exclude_end?
    new(field).greater_than_or_equal_to(value.first).less_than(value.last)
  else
    new(field).greater_than_or_equal_to(value.first).less_than_or_equal_to(value.last)
  end
end
build_raw_query(query) click to toggle source
# File lib/lsolr.rb, line 156
def build_raw_query(query)
  query.empty? ? new : new.raw(query)
end

Public Instance Methods

and(another) click to toggle source

Builds a composite query expression.

@see lucene.apache.org/solr/guide/7_2/the-standard-query-parser.html#the-boolean-operator-and The Boolean Operator AND (“&&”)

@param another [LSolr, Hash, String] another query builder instance or query params or raw query string

@return [LSolr] copied another query builder instance

# File lib/lsolr.rb, line 448
def and(another)
  link(another, AND)
end
blank?() click to toggle source

A query is blank if term is incomplete in expression.

@return [true, false]

# File lib/lsolr.rb, line 202
def blank?
  managed_query_absence = @field.empty? || (@value.empty? && (@range_first.empty? || @range_last.empty?))
  managed_query_absence && @raw.empty?
end
boost(factor) click to toggle source

Boosts a query expression.

@see lucene.apache.org/solr/guide/7_2/the-standard-query-parser.html#boosting-a-term-with Boosting a Term with “^”

@param factor [Float] a boost factor number

@return [LSolr] self instance

@raise [LSolr::ArgumentError] if specified boost factor is invalid

# File lib/lsolr.rb, line 274
def boost(factor)
  raise ArgumentError, "The boost factor must be a positive number. `#{factor}` given." unless valid_boost_factor?(factor)

  @boost = "#{BOOST}#{factor}"
  self
end
constant_score(score) click to toggle source

Specifies scoring result in expression.

@see lucene.apache.org/solr/guide/7_2/the-standard-query-parser.html#constant-score-with Constant Score with “^=”

@param score [Float] a constant score

@return [LSolr] self instance

@raise [LSolr::ArgumentError] if specified score number is invalid

# File lib/lsolr.rb, line 290
def constant_score(score)
  raise ArgumentError, "The constant score must be a number. `#{score}` given." unless valid_score?(score)

  @constant_score = "#{CONSTANT_SCORE}#{score}"
  self
end
date_time_match(value) click to toggle source

Builds a normal query expression with dates and times.

@see lucene.apache.org/solr/guide/7_2/working-with-dates.html Working with Dates

@param value [String, Date, Time] a filter value

@return [LSolr] self instance

# File lib/lsolr.rb, line 342
def date_time_match(value)
  value = stringify(value, symbols: RESERVED_SYMBOLS - %w[- : . / +])
  @value = %("#{value}")
  self
end
field(name) click to toggle source

Sets a field name.

@param name [String, Symbol] a field name

@return [LSolr] self instance

@raise [LSolr::ArgumentError] if specified field name is empty

# File lib/lsolr.rb, line 221
def field(name)
  raise ArgumentError, "The field name must be a not empty string value. `#{name}` given." unless present_string?(name)

  @field = name.to_s
  self
end
fuzzy_match(value, distance: 2.0) click to toggle source

Builds a fuzzy search query expression.

@see lucene.apache.org/solr/guide/7_2/the-standard-query-parser.html#fuzzy-searches Fuzzy Searches

@param value [String] a search word @param distance [Float] a proximity distance

@return [LSolr] self instance

@raise [LSolr::ArgumentError] if specified distance is out of range

# File lib/lsolr.rb, line 386
def fuzzy_match(value, distance: 2.0)
  raise ArgumentError, "Out of #{RANGE_FUZZY_MATCH_DISTANCE}. `#{distance}` given." unless valid_fuzzy_match_distance?(distance)

  @value = "#{clean(value).split.join}#{PROXIMITY}#{distance}"
  self
end
greater_than(value) click to toggle source

Builds a range search query expression.

@see lucene.apache.org/solr/guide/7_2/the-standard-query-parser.html#range-searches Range Searches

@param value [String, Integer, Date, Time] a filter value

@return [LSolr] self instance

# File lib/lsolr.rb, line 400
def greater_than(value)
  @range_first = "#{GREATER_THAN}#{stringify(value)}"
  self
end
greater_than_or_equal_to(value) click to toggle source

Builds a range search query expression.

@see lucene.apache.org/solr/guide/7_2/the-standard-query-parser.html#range-searches Range Searches

@param value [String, Integer, Date, Time] a filter value

@return [LSolr] self instance

# File lib/lsolr.rb, line 424
def greater_than_or_equal_to(value)
  @range_first = "#{GREATER_THAN_OR_EQUAL_TO}#{stringify(value)}"
  self
end
head() click to toggle source

Returns a first term of query.

@return [LSolr] a first term of query.

# File lib/lsolr.rb, line 466
def head
  if present_query?(prev)
    prev.head
  else
    self
  end
end
inspect() click to toggle source

Returns instance information.

@return [String] instance information

# File lib/lsolr.rb, line 193
def inspect
  format(FORMAT_INSPECT, class: self.class.name,
                         object: object_id << 1,
                         query: present? ? to_s : '')
end
less_than(value) click to toggle source

Builds a range search query expression.

@see lucene.apache.org/solr/guide/7_2/the-standard-query-parser.html#range-searches Range Searches

@param value [String, Integer, Date, Time] a filter value

@return [LSolr] self instance

# File lib/lsolr.rb, line 412
def less_than(value)
  @range_last = "#{stringify(value)}#{LESS_THAN}"
  self
end
less_than_or_equal_to(value) click to toggle source

Builds a range search query expression.

@see lucene.apache.org/solr/guide/7_2/the-standard-query-parser.html#range-searches Range Searches

@param value [String, Integer, Date, Time] a filter value

@return [LSolr] self instance

# File lib/lsolr.rb, line 436
def less_than_or_equal_to(value)
  @range_last = "#{stringify(value)}#{LESS_THAN_OR_EQUAL_TO}"
  self
end
match(value) click to toggle source

Builds a normal query expression.

@param value [String, Integer, true, false] a search word or a filter value

@return [LSolr] self instance

@raise [LSolr::ArgumentError] if specified value is empty

# File lib/lsolr.rb, line 304
def match(value)
  raise ArgumentError, "`#{value}` given. It must be a not empty value." unless present_string?(value.to_s)
  return match_in(value) if value.is_a?(Array)
  return date_time_match(value) if value.is_a?(Date) || value.is_a?(Time)

  values = clean(value).split

  if values.size > 1
    phrase_match(values)
  else
    @value = values.join
    self
  end
end
match_in(values) click to toggle source

Builds a normal multi value query expression.

@param value [Array<String, Symbol, Integer>] a search words or a filter values

@return [LSolr] self instance

@raise [LSolr::ArgumentError] if specified value is a empty array or not array

# File lib/lsolr.rb, line 326
def match_in(values)
  raise ArgumentError, "`#{values}` given. It must be a not empty array." unless present_array?(values)
  return match(values.first) if values.size == 1

  values = values.map { |v| clean(v) }
  @value = "(#{values.join(DELIMITER_SPACE)})"
  self
end
not() click to toggle source

Adds the boolean operator `NOT` to query expression.

@see lucene.apache.org/solr/guide/7_2/the-standard-query-parser.html#the-boolean-operator-not The Boolean Operator NOT (“!”)

@return [LSolr] self instance

# File lib/lsolr.rb, line 259
def not
  this = dup
  this.head.expr_not = "#{NOT} "
  this
end
or(another) click to toggle source

Builds a composite query expression.

@see lucene.apache.org/solr/guide/7_2/the-standard-query-parser.html#boolean-operators-supported-by-the-standard-query-parser Boolean Operators Supported by the Standard Query Parser

@param another [LSolr, Hash, String] another query builder instance or query params or raw query string

@return [LSolr] copied another query builder instance

# File lib/lsolr.rb, line 459
def or(another)
  link(another, OR)
end
phrase_match(values, distance: 0) click to toggle source

Builds a phrase or proximity search query expression.

@see lucene.apache.org/solr/guide/7_2/the-standard-query-parser.html#grouping-clauses-within-a-field Grouping Clauses within a Field @see lucene.apache.org/solr/guide/7_2/the-standard-query-parser.html#proximity-searches Proximity Searches

@param values [Array<String>] search words @param distance [Integer] proximity distance

@return [LSolr] self instance

# File lib/lsolr.rb, line 369
def phrase_match(values, distance: 0)
  value = values.map { |v| clean(v).split }.flatten.join(DELIMITER_SPACE)
  proximity_match = distance.to_s.to_i > 0 ? "#{PROXIMITY}#{distance}" : ''
  @value = %("#{value}"#{proximity_match})
  self
end
prefix_match(value) click to toggle source

Builds a prefix search query expression.

@see lucene.apache.org/solr/guide/7_2/the-standard-query-parser.html#wildcard-searches Wildcard Searches

@param value [String] a search word

@return [LSolr] self instance

# File lib/lsolr.rb, line 355
def prefix_match(value)
  @value = clean(value, symbols: RESERVED_SYMBOLS - %w[* ?]).split.join(WILD_CARD)
  self
end
present?() click to toggle source

A query is present if it's not blank.

@return [true, false]

# File lib/lsolr.rb, line 210
def present?
  !blank?
end
raw(query) click to toggle source

Sets a raw query.

@param query [String] a raw query string

@return [LSolr] self instance

@raise [LSolr::ArgumentError] if specified raw query string is empty

# File lib/lsolr.rb, line 235
def raw(query)
  raise ArgumentError, "The raw query must be a not empty string value. `#{query}` given." unless present_string?(query)

  @raw = query.to_s
  self
end
to_s() click to toggle source

Returns Apache Solr standard lucene type query string.

@return [String] a stringified query

@raise [LSolr::IncompleteQueryError] if the query is incompletely

# File lib/lsolr.rb, line 182
def to_s
  raise IncompleteQueryError, 'Please specify a term of search.' if blank?

  decorate_linked_expressions_if_needed(build_expression)
end
Also aliased as: to_str
to_str()
Alias for: to_s
wrap() click to toggle source

Adds parentheses to query expression.

@see lucene.apache.org/solr/guide/7_2/the-standard-query-parser.html#grouping-terms-to-form-sub-queries Grouping Terms to Form Sub-Queries

@return [LSolr] copied self instance

# File lib/lsolr.rb, line 247
def wrap
  this = dup
  this.head.left_parentheses << PARENTHESIS_LEFT
  this.right_parentheses << PARENTHESIS_RIGHT
  this
end

Private Instance Methods

build_expression() click to toggle source
# File lib/lsolr.rb, line 558
def build_expression
  if raw?
    @raw
  elsif range_search?
    "#{@field}:#{@range_first} #{TO} #{@range_last}"
  else
    "#{@field}:#{@value}"
  end
end
build_instance_if_needed(another) click to toggle source
# File lib/lsolr.rb, line 551
def build_instance_if_needed(another)
  case another
  when self.class then another
  when Hash, String then self.class.build(another)
  end
end
clean(value, symbols: RESERVED_SYMBOLS) click to toggle source
# File lib/lsolr.rb, line 514
def clean(value, symbols: RESERVED_SYMBOLS)
  value.to_s
       .tr(symbols.join, REPLACEMENT_CHAR)
       .gsub(RESERVED_WORDS) { |match| "\\#{match}" }
end
decorate_linked_expressions_if_needed(expr) click to toggle source
# File lib/lsolr.rb, line 568
def decorate_linked_expressions_if_needed(expr)
  expr = "#{expr_not}#{left_parentheses.join}#{expr}#{right_parentheses.join}"
  expr = "#{prev} #{operator} #{expr}" if present_query?(prev)
  scoring = present_string?(@constant_score) ? @constant_score : @boost
  "#{expr}#{scoring}"
end
format_date(date) click to toggle source
# File lib/lsolr.rb, line 528
def format_date(date)
  msec_str = case date
             when Date then date.strftime(FORMAT_MILLISECOND_FOR_DATE_TYPE).gsub(date.strftime(FORMAT_SECOND), '')
             when Time then date.strftime(FORMAT_MILLISECOND_FOR_TIME_TYPE)
             else raise TypeError, "Could not format dates or times. `#{date}` given."
             end

  return date.strftime(FORMAT_DATE_TIME) if msec_str == '000'

  "#{date.strftime('%Y-%m-%dT%H:%M:%S')}.#{msec_str}Z"
end
initialize_copy(obj) click to toggle source
# File lib/lsolr.rb, line 476
def initialize_copy(obj)
  obj.prev = obj.prev.dup if present_query?(obj.prev)
  obj.left_parentheses = obj.left_parentheses.dup
  obj.right_parentheses = obj.right_parentheses.dup
end
present_array?(val) click to toggle source
# File lib/lsolr.rb, line 494
def present_array?(val)
  !val.nil? && val.is_a?(Array) && !val.compact.empty? && val.map(&:to_s).map(&:empty?).none?
end
present_query?(val) click to toggle source
# File lib/lsolr.rb, line 498
def present_query?(val)
  !val.nil? && val.present?
end
present_string?(val) click to toggle source
# File lib/lsolr.rb, line 490
def present_string?(val)
  !val.nil? && (val.is_a?(String) || val.is_a?(Symbol)) && !val.empty?
end
range_search?() click to toggle source
# File lib/lsolr.rb, line 482
def range_search?
  @value.empty? && !@range_first.empty? && !@range_last.empty?
end
raw?() click to toggle source
# File lib/lsolr.rb, line 486
def raw?
  !@raw.empty?
end
stringify(value, symbols: RESERVED_SYMBOLS - %w[- : . / + *]) click to toggle source
# File lib/lsolr.rb, line 520
def stringify(value, symbols: RESERVED_SYMBOLS - %w[- : . / + *])
  if value.is_a?(Date) || value.is_a?(Time)
    format_date(value)
  else
    clean(value, symbols: symbols)
  end
end
valid_boost_factor?(val) click to toggle source
# File lib/lsolr.rb, line 502
def valid_boost_factor?(val)
  (val.is_a?(Float) || val.is_a?(Integer)) && val > 0
end
valid_fuzzy_match_distance?(val) click to toggle source
# File lib/lsolr.rb, line 510
def valid_fuzzy_match_distance?(val)
  (val.is_a?(Float) || val.is_a?(Integer)) && RANGE_FUZZY_MATCH_DISTANCE.member?(val)
end
valid_score?(val) click to toggle source
# File lib/lsolr.rb, line 506
def valid_score?(val)
  val.is_a?(Float) || val.is_a?(Integer)
end