Class: LSolr

Inherits:
Object
  • Object
show all
Defined in:
lib/lsolr.rb

Overview

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

Examples:

How to use. Part 1:

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

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]'

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}))'

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*)'

How to use. Part 5:

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

Constant Summary collapse

ArgumentError =
Class.new(::ArgumentError)
TypeError =
Class.new(::TypeError)
IncompleteQueryError =
Class.new(StandardError)
NOT =
'NOT'
AND =
'AND'
OR =
'OR'
TO =
'TO'
GREATER_THAN =
'{'
LESS_THAN =
'}'
GREATER_THAN_OR_EQUAL_TO =
'['
LESS_THAN_OR_EQUAL_TO =
']'
WILD_CARD =
'*'
PROXIMITY =
'~'
BOOST =
'^'
CONSTANT_SCORE =
'^='
DELIMITER_SPACE =
' '
RANGE_FUZZY_MATCH_DISTANCE =
(0.0..2.0).freeze
FORMAT_DATE_TIME =
'%Y-%m-%dT%H:%M:%SZ'
FORMAT_MILLISECOND_FOR_DATE_TYPE =
'%Q'
FORMAT_MILLISECOND_FOR_TIME_TYPE =
'%L'
FORMAT_SECOND =
'%s'
FORMAT_INSPECT =
'#<%<class>s:%<object>#018x `%<query>s`>'
PARENTHESIS_LEFT =
'('
PARENTHESIS_RIGHT =
')'
RESERVED_SYMBOLS =
%w(- + & | ! ( ) { } [ ] ^ " ~ * ? : \\\\ /).freeze
RESERVED_WORDS =
/(AND|OR|NOT)/.freeze
REPLACEMENT_CHAR =
' '

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(field_name = nil) ⇒ LSolr

Create a new query builder instance.

Parameters:

  • field_name (String, Symbol) (defaults to: nil)

    a field name



165
166
167
168
169
170
171
172
173
174
175
# 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

Instance Attribute Details

#expr_notObject

Returns the value of attribute expr_not.



104
105
106
# File 'lib/lsolr.rb', line 104

def expr_not
  @expr_not
end

#left_parenthesesObject

Returns the value of attribute left_parentheses.



104
105
106
# File 'lib/lsolr.rb', line 104

def left_parentheses
  @left_parentheses
end

#operatorObject

Returns the value of attribute operator.



104
105
106
# File 'lib/lsolr.rb', line 104

def operator
  @operator
end

#prevObject

Returns the value of attribute prev.



104
105
106
# File 'lib/lsolr.rb', line 104

def prev
  @prev
end

#right_parenthesesObject

Returns the value of attribute right_parentheses.



104
105
106
# File 'lib/lsolr.rb', line 104

def right_parentheses
  @right_parentheses
end

Class Method Details

.build(params) ⇒ LSolr

Builds composite query and returns builder instance.

Parameters:

  • params (Hash{Symbol => String, Symbol, Integer, Float, true, false, Range, Date, Time, Array<String, Symbol, Integer>}, String)

    query terms or a raw query

Returns:

Raises:



114
115
116
117
118
119
120
121
122
# 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

Instance Method Details

#and(another) ⇒ LSolr

Builds a composite query expression.

Parameters:

  • another (LSolr, Hash, String)

    another query builder instance or query params or raw query string

Returns:

  • (LSolr)

    copied another query builder instance

See Also:



448
449
450
# File 'lib/lsolr.rb', line 448

def and(another)
  link(another, AND)
end

#blank?true, false

A query is blank if term is incomplete in expression.

Returns:

  • (true, false)


202
203
204
205
# 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) ⇒ LSolr

Boosts a query expression.

Parameters:

  • factor (Float)

    a boost factor number

Returns:

  • (LSolr)

    self instance

Raises:

See Also:



274
275
276
277
278
279
# 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) ⇒ LSolr

Specifies scoring result in expression.

Parameters:

  • score (Float)

    a constant score

Returns:

  • (LSolr)

    self instance

Raises:

See Also:



290
291
292
293
294
295
# 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) ⇒ LSolr

Builds a normal query expression with dates and times.

Parameters:

  • value (String, Date, Time)

    a filter value

Returns:

  • (LSolr)

    self instance

See Also:



342
343
344
345
346
# File 'lib/lsolr.rb', line 342

def date_time_match(value)
  value = stringify(value, symbols: RESERVED_SYMBOLS - %w[- : . / +])
  @value = %("#{value}")
  self
end

#field(name) ⇒ LSolr

Sets a field name.

Parameters:

  • name (String, Symbol)

    a field name

Returns:

  • (LSolr)

    self instance

Raises:



221
222
223
224
225
226
# 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) ⇒ LSolr

Builds a fuzzy search query expression.

Parameters:

  • value (String)

    a search word

  • distance (Float) (defaults to: 2.0)

    a proximity distance

Returns:

  • (LSolr)

    self instance

Raises:

See Also:



386
387
388
389
390
391
# 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) ⇒ LSolr

Builds a range search query expression.

Parameters:

  • value (String, Integer, Date, Time)

    a filter value

Returns:

  • (LSolr)

    self instance

See Also:



400
401
402
403
# File 'lib/lsolr.rb', line 400

def greater_than(value)
  @range_first = "#{GREATER_THAN}#{stringify(value)}"
  self
end

#greater_than_or_equal_to(value) ⇒ LSolr

Builds a range search query expression.

Parameters:

  • value (String, Integer, Date, Time)

    a filter value

Returns:

  • (LSolr)

    self instance

See Also:



424
425
426
427
# File 'lib/lsolr.rb', line 424

def greater_than_or_equal_to(value)
  @range_first = "#{GREATER_THAN_OR_EQUAL_TO}#{stringify(value)}"
  self
end

#headLSolr

Returns a first term of query.

Returns:

  • (LSolr)

    a first term of query.



466
467
468
469
470
471
472
# File 'lib/lsolr.rb', line 466

def head
  if present_query?(prev)
    prev.head
  else
    self
  end
end

#inspectString

Returns instance information.

Returns:

  • (String)

    instance information



193
194
195
196
197
# 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) ⇒ LSolr

Builds a range search query expression.

Parameters:

  • value (String, Integer, Date, Time)

    a filter value

Returns:

  • (LSolr)

    self instance

See Also:



412
413
414
415
# File 'lib/lsolr.rb', line 412

def less_than(value)
  @range_last = "#{stringify(value)}#{LESS_THAN}"
  self
end

#less_than_or_equal_to(value) ⇒ LSolr

Builds a range search query expression.

Parameters:

  • value (String, Integer, Date, Time)

    a filter value

Returns:

  • (LSolr)

    self instance

See Also:



436
437
438
439
# 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) ⇒ LSolr

Builds a normal query expression.

Parameters:

  • value (String, Integer, true, false)

    a search word or a filter value

Returns:

  • (LSolr)

    self instance

Raises:



304
305
306
307
308
309
310
311
312
313
314
315
316
317
# 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) ⇒ LSolr

Builds a normal multi value query expression.

Parameters:

  • value (Array<String, Symbol, Integer>)

    a search words or a filter values

Returns:

  • (LSolr)

    self instance

Raises:



326
327
328
329
330
331
332
333
# 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

#notLSolr

Adds the boolean operator ‘NOT` to query expression.

Returns:

  • (LSolr)

    self instance

See Also:



259
260
261
262
263
# File 'lib/lsolr.rb', line 259

def not
  this = dup
  this.head.expr_not = "#{NOT} "
  this
end

#or(another) ⇒ LSolr

Builds a composite query expression.

Parameters:

  • another (LSolr, Hash, String)

    another query builder instance or query params or raw query string

Returns:

  • (LSolr)

    copied another query builder instance

See Also:



459
460
461
# File 'lib/lsolr.rb', line 459

def or(another)
  link(another, OR)
end

#phrase_match(values, distance: 0) ⇒ LSolr

Builds a phrase or proximity search query expression.

Parameters:

  • values (Array<String>)

    search words

  • distance (Integer) (defaults to: 0)

    proximity distance

Returns:

  • (LSolr)

    self instance

See Also:



369
370
371
372
373
374
# 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) ⇒ LSolr

Builds a prefix search query expression.

Parameters:

  • value (String)

    a search word

Returns:

  • (LSolr)

    self instance

See Also:



355
356
357
358
# File 'lib/lsolr.rb', line 355

def prefix_match(value)
  @value = clean(value, symbols: RESERVED_SYMBOLS - %w[* ?]).split.join(WILD_CARD)
  self
end

#present?true, false

A query is present if it’s not blank.

Returns:

  • (true, false)


210
211
212
# File 'lib/lsolr.rb', line 210

def present?
  !blank?
end

#raw(query) ⇒ LSolr

Sets a raw query.

Parameters:

  • query (String)

    a raw query string

Returns:

  • (LSolr)

    self instance

Raises:



235
236
237
238
239
240
# 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_sString Also known as: to_str

Returns Apache Solr standard lucene type query string.

Returns:

  • (String)

    a stringified query

Raises:



182
183
184
185
186
# 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

#wrapLSolr

Adds parentheses to query expression.

Returns:

  • (LSolr)

    copied self instance

See Also:



247
248
249
250
251
252
# File 'lib/lsolr.rb', line 247

def wrap
  this = dup
  this.head.left_parentheses << PARENTHESIS_LEFT
  this.right_parentheses << PARENTHESIS_RIGHT
  this
end