gojee-sunspot 2.0.3 → 2.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +12 -0
- data/Gemfile +5 -0
- data/History.txt +252 -0
- data/LICENSE +18 -0
- data/Rakefile +13 -0
- data/TODO +13 -0
- data/lib/light_config.rb +40 -0
- data/lib/sunspot/adapters.rb +265 -0
- data/lib/sunspot/batcher.rb +62 -0
- data/lib/sunspot/class_set.rb +23 -0
- data/lib/sunspot/composite_setup.rb +202 -0
- data/lib/sunspot/configuration.rb +53 -0
- data/lib/sunspot/data_extractor.rb +50 -0
- data/lib/sunspot/dsl/adjustable.rb +47 -0
- data/lib/sunspot/dsl/field_group.rb +57 -0
- data/lib/sunspot/dsl/field_query.rb +327 -0
- data/lib/sunspot/dsl/fields.rb +103 -0
- data/lib/sunspot/dsl/fulltext.rb +243 -0
- data/lib/sunspot/dsl/function.rb +27 -0
- data/lib/sunspot/dsl/functional.rb +44 -0
- data/lib/sunspot/dsl/more_like_this_query.rb +56 -0
- data/lib/sunspot/dsl/paginatable.rb +32 -0
- data/lib/sunspot/dsl/query_facet.rb +36 -0
- data/lib/sunspot/dsl/restriction.rb +25 -0
- data/lib/sunspot/dsl/restriction_with_near.rb +160 -0
- data/lib/sunspot/dsl/scope.rb +217 -0
- data/lib/sunspot/dsl/search.rb +30 -0
- data/lib/sunspot/dsl/standard_query.rb +123 -0
- data/lib/sunspot/dsl.rb +5 -0
- data/lib/sunspot/field.rb +193 -0
- data/lib/sunspot/field_factory.rb +129 -0
- data/lib/sunspot/indexer.rb +136 -0
- data/lib/sunspot/query/abstract_field_facet.rb +52 -0
- data/lib/sunspot/query/bbox.rb +15 -0
- data/lib/sunspot/query/boost_query.rb +24 -0
- data/lib/sunspot/query/common_query.rb +96 -0
- data/lib/sunspot/query/composite_fulltext.rb +36 -0
- data/lib/sunspot/query/connective.rb +206 -0
- data/lib/sunspot/query/date_field_facet.rb +14 -0
- data/lib/sunspot/query/dismax.rb +132 -0
- data/lib/sunspot/query/field_facet.rb +41 -0
- data/lib/sunspot/query/field_group.rb +36 -0
- data/lib/sunspot/query/filter.rb +38 -0
- data/lib/sunspot/query/function_query.rb +52 -0
- data/lib/sunspot/query/geo.rb +53 -0
- data/lib/sunspot/query/geofilt.rb +16 -0
- data/lib/sunspot/query/highlighting.rb +62 -0
- data/lib/sunspot/query/more_like_this.rb +61 -0
- data/lib/sunspot/query/more_like_this_query.rb +12 -0
- data/lib/sunspot/query/pagination.rb +42 -0
- data/lib/sunspot/query/query_facet.rb +16 -0
- data/lib/sunspot/query/restriction.rb +262 -0
- data/lib/sunspot/query/scope.rb +9 -0
- data/lib/sunspot/query/sort.rb +109 -0
- data/lib/sunspot/query/sort_composite.rb +34 -0
- data/lib/sunspot/query/standard_query.rb +16 -0
- data/lib/sunspot/query/text_field_boost.rb +17 -0
- data/lib/sunspot/query.rb +11 -0
- data/lib/sunspot/schema.rb +151 -0
- data/lib/sunspot/search/abstract_search.rb +281 -0
- data/lib/sunspot/search/date_facet.rb +35 -0
- data/lib/sunspot/search/facet_row.rb +27 -0
- data/lib/sunspot/search/field_facet.rb +88 -0
- data/lib/sunspot/search/field_group.rb +32 -0
- data/lib/sunspot/search/group.rb +50 -0
- data/lib/sunspot/search/highlight.rb +38 -0
- data/lib/sunspot/search/hit.rb +150 -0
- data/lib/sunspot/search/hit_enumerable.rb +72 -0
- data/lib/sunspot/search/more_like_this_search.rb +31 -0
- data/lib/sunspot/search/paginated_collection.rb +57 -0
- data/lib/sunspot/search/query_facet.rb +67 -0
- data/lib/sunspot/search/standard_search.rb +21 -0
- data/lib/sunspot/search.rb +9 -0
- data/lib/sunspot/session.rb +262 -0
- data/lib/sunspot/session_proxy/abstract_session_proxy.rb +29 -0
- data/lib/sunspot/session_proxy/class_sharding_session_proxy.rb +66 -0
- data/lib/sunspot/session_proxy/id_sharding_session_proxy.rb +89 -0
- data/lib/sunspot/session_proxy/master_slave_session_proxy.rb +43 -0
- data/lib/sunspot/session_proxy/multicore_session_proxy.rb +67 -0
- data/lib/sunspot/session_proxy/sharding_session_proxy.rb +222 -0
- data/lib/sunspot/session_proxy/silent_fail_session_proxy.rb +42 -0
- data/lib/sunspot/session_proxy/thread_local_session_proxy.rb +37 -0
- data/lib/sunspot/session_proxy.rb +95 -0
- data/lib/sunspot/setup.rb +350 -0
- data/lib/sunspot/text_field_setup.rb +29 -0
- data/lib/sunspot/type.rb +393 -0
- data/lib/sunspot/util.rb +252 -0
- data/lib/sunspot/version.rb +3 -0
- data/lib/sunspot.rb +579 -0
- data/log/.gitignore +1 -0
- data/pkg/.gitignore +1 -0
- data/script/console +10 -0
- data/spec/api/adapters_spec.rb +33 -0
- data/spec/api/batcher_spec.rb +112 -0
- data/spec/api/binding_spec.rb +50 -0
- data/spec/api/class_set_spec.rb +24 -0
- data/spec/api/hit_enumerable_spec.rb +47 -0
- data/spec/api/indexer/attributes_spec.rb +149 -0
- data/spec/api/indexer/batch_spec.rb +72 -0
- data/spec/api/indexer/dynamic_fields_spec.rb +42 -0
- data/spec/api/indexer/fixed_fields_spec.rb +57 -0
- data/spec/api/indexer/fulltext_spec.rb +43 -0
- data/spec/api/indexer/removal_spec.rb +53 -0
- data/spec/api/indexer/spec_helper.rb +1 -0
- data/spec/api/indexer_spec.rb +14 -0
- data/spec/api/query/advanced_manipulation_examples.rb +35 -0
- data/spec/api/query/connectives_examples.rb +189 -0
- data/spec/api/query/dsl_spec.rb +18 -0
- data/spec/api/query/dynamic_fields_examples.rb +165 -0
- data/spec/api/query/faceting_examples.rb +397 -0
- data/spec/api/query/fulltext_examples.rb +313 -0
- data/spec/api/query/function_spec.rb +79 -0
- data/spec/api/query/geo_examples.rb +68 -0
- data/spec/api/query/group_spec.rb +32 -0
- data/spec/api/query/highlighting_examples.rb +245 -0
- data/spec/api/query/more_like_this_spec.rb +140 -0
- data/spec/api/query/ordering_pagination_examples.rb +116 -0
- data/spec/api/query/scope_examples.rb +275 -0
- data/spec/api/query/spatial_examples.rb +27 -0
- data/spec/api/query/spec_helper.rb +1 -0
- data/spec/api/query/standard_spec.rb +29 -0
- data/spec/api/query/text_field_scoping_examples.rb +30 -0
- data/spec/api/query/types_spec.rb +20 -0
- data/spec/api/search/dynamic_fields_spec.rb +33 -0
- data/spec/api/search/faceting_spec.rb +360 -0
- data/spec/api/search/highlighting_spec.rb +69 -0
- data/spec/api/search/hits_spec.rb +131 -0
- data/spec/api/search/paginated_collection_spec.rb +36 -0
- data/spec/api/search/results_spec.rb +72 -0
- data/spec/api/search/search_spec.rb +23 -0
- data/spec/api/search/spec_helper.rb +1 -0
- data/spec/api/session_proxy/class_sharding_session_proxy_spec.rb +85 -0
- data/spec/api/session_proxy/id_sharding_session_proxy_spec.rb +30 -0
- data/spec/api/session_proxy/master_slave_session_proxy_spec.rb +41 -0
- data/spec/api/session_proxy/sharding_session_proxy_spec.rb +77 -0
- data/spec/api/session_proxy/silent_fail_session_proxy_spec.rb +24 -0
- data/spec/api/session_proxy/spec_helper.rb +9 -0
- data/spec/api/session_proxy/thread_local_session_proxy_spec.rb +39 -0
- data/spec/api/session_spec.rb +232 -0
- data/spec/api/spec_helper.rb +3 -0
- data/spec/api/sunspot_spec.rb +29 -0
- data/spec/ext.rb +11 -0
- data/spec/helpers/indexer_helper.rb +17 -0
- data/spec/helpers/integration_helper.rb +8 -0
- data/spec/helpers/mock_session_helper.rb +13 -0
- data/spec/helpers/query_helper.rb +26 -0
- data/spec/helpers/search_helper.rb +68 -0
- data/spec/integration/dynamic_fields_spec.rb +57 -0
- data/spec/integration/faceting_spec.rb +251 -0
- data/spec/integration/field_grouping_spec.rb +66 -0
- data/spec/integration/geospatial_spec.rb +85 -0
- data/spec/integration/highlighting_spec.rb +44 -0
- data/spec/integration/indexing_spec.rb +55 -0
- data/spec/integration/keyword_search_spec.rb +317 -0
- data/spec/integration/local_search_spec.rb +64 -0
- data/spec/integration/more_like_this_spec.rb +43 -0
- data/spec/integration/scoped_search_spec.rb +354 -0
- data/spec/integration/stored_fields_spec.rb +12 -0
- data/spec/integration/test_pagination.rb +43 -0
- data/spec/integration/unicode_spec.rb +15 -0
- data/spec/mocks/adapters.rb +32 -0
- data/spec/mocks/blog.rb +3 -0
- data/spec/mocks/comment.rb +21 -0
- data/spec/mocks/connection.rb +126 -0
- data/spec/mocks/mock_adapter.rb +30 -0
- data/spec/mocks/mock_class_sharding_session_proxy.rb +24 -0
- data/spec/mocks/mock_record.rb +52 -0
- data/spec/mocks/mock_sharding_session_proxy.rb +15 -0
- data/spec/mocks/photo.rb +11 -0
- data/spec/mocks/post.rb +86 -0
- data/spec/mocks/super_class.rb +2 -0
- data/spec/mocks/user.rb +13 -0
- data/spec/spec_helper.rb +40 -0
- data/sunspot.gemspec +42 -0
- data/tasks/rdoc.rake +27 -0
- data/tasks/schema.rake +19 -0
- data/tasks/todo.rake +4 -0
- metadata +261 -3
@@ -0,0 +1,16 @@
|
|
1
|
+
module Sunspot
|
2
|
+
module Query
|
3
|
+
class Geofilt
|
4
|
+
def initialize(field, lat, lon, radius, options = {})
|
5
|
+
@field, @lat, @lon, @radius, @options = field, lat, lon, radius, options
|
6
|
+
end
|
7
|
+
|
8
|
+
def to_params
|
9
|
+
func = @options[:bbox] ? "bbox" : "geofilt"
|
10
|
+
|
11
|
+
filter = "{!#{func} sfield=#{@field.indexed_name} pt=#{@lat},#{@lon} d=#{@radius}}"
|
12
|
+
{:fq => filter}
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Sunspot
|
2
|
+
module Query
|
3
|
+
#
|
4
|
+
# A query component that builds parameters for requesting highlights
|
5
|
+
#
|
6
|
+
class Highlighting #:nodoc:
|
7
|
+
def initialize(fields=[], options={})
|
8
|
+
@fields = fields
|
9
|
+
@options = options
|
10
|
+
end
|
11
|
+
|
12
|
+
#
|
13
|
+
# Return Solr highlighting params
|
14
|
+
#
|
15
|
+
def to_params
|
16
|
+
params = {
|
17
|
+
:hl => 'on',
|
18
|
+
:"hl.simple.pre" => '@@@hl@@@',
|
19
|
+
:"hl.simple.post" => '@@@endhl@@@'
|
20
|
+
}
|
21
|
+
unless @fields.empty?
|
22
|
+
params[:"hl.fl"] = @fields.map { |field| field.indexed_name }
|
23
|
+
end
|
24
|
+
if max_snippets = @options[:max_snippets]
|
25
|
+
params.merge!(make_params('snippets', max_snippets))
|
26
|
+
end
|
27
|
+
if fragment_size = @options[:fragment_size]
|
28
|
+
params.merge!(make_params('fragsize', fragment_size))
|
29
|
+
end
|
30
|
+
if @options[:merge_contiguous_fragments]
|
31
|
+
params.merge!(make_params('mergeContiguous', 'true'))
|
32
|
+
end
|
33
|
+
if @options[:phrase_highlighter]
|
34
|
+
params.merge!(make_params('usePhraseHighlighter', 'true'))
|
35
|
+
if @options[:require_field_match]
|
36
|
+
params.merge!(make_params('requireFieldMatch', 'true'))
|
37
|
+
end
|
38
|
+
end
|
39
|
+
if formatter = @options[:formatter]
|
40
|
+
params.merge!(make_params('formatter', formatter))
|
41
|
+
end
|
42
|
+
if fragmenter = @options[:fragmenter]
|
43
|
+
params.merge!(make_params('fragmenter', fragmenter))
|
44
|
+
end
|
45
|
+
|
46
|
+
params
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def make_params(name, value)
|
52
|
+
if @fields.empty?
|
53
|
+
{ :"hl.#{name}" => value }
|
54
|
+
else
|
55
|
+
@fields.inject({}) do |hash, field|
|
56
|
+
hash.merge!(:"f.#{field.indexed_name}.hl.#{name}" => value)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Sunspot
|
2
|
+
module Query
|
3
|
+
class MoreLikeThis
|
4
|
+
attr_reader :fields
|
5
|
+
|
6
|
+
def initialize(document)
|
7
|
+
@document_scope = Restriction::EqualTo.new(
|
8
|
+
false,
|
9
|
+
IdField.instance,
|
10
|
+
Adapters::InstanceAdapter.adapt(document).index_id
|
11
|
+
)
|
12
|
+
@fields = {}
|
13
|
+
@params = {}
|
14
|
+
end
|
15
|
+
|
16
|
+
def add_field(field, boost = nil)
|
17
|
+
raise(ArgumentError, "Field #{field.name} is not set up for more_like_this") unless field.more_like_this?
|
18
|
+
@fields[field.indexed_name] = TextFieldBoost.new(field, boost)
|
19
|
+
end
|
20
|
+
|
21
|
+
def minimum_term_frequency=(mintf)
|
22
|
+
@params[:"mlt.mintf"] = mintf
|
23
|
+
end
|
24
|
+
|
25
|
+
def minimum_document_frequency=(mindf)
|
26
|
+
@params[:"mlt.mindf"] = mindf
|
27
|
+
end
|
28
|
+
|
29
|
+
def minimum_word_length=(minwl)
|
30
|
+
@params[:"mlt.minwl"] = minwl
|
31
|
+
end
|
32
|
+
|
33
|
+
def maximum_word_length=(maxwl)
|
34
|
+
@params[:"mlt.maxwl"] = maxwl
|
35
|
+
end
|
36
|
+
|
37
|
+
def maximum_query_terms=(maxqt)
|
38
|
+
@params[:"mlt.maxqt"] = maxqt
|
39
|
+
end
|
40
|
+
|
41
|
+
def boost_by_relevance=(should_boost)
|
42
|
+
@params[:"mlt.boost"] = should_boost
|
43
|
+
end
|
44
|
+
|
45
|
+
def to_params
|
46
|
+
params = Sunspot::Util.deep_merge(
|
47
|
+
@params,
|
48
|
+
:q => @document_scope.to_boolean_phrase
|
49
|
+
)
|
50
|
+
params[:"mlt.fl"] = @fields.keys.join(",")
|
51
|
+
boosted_fields = @fields.values.select { |field| field.boost }
|
52
|
+
unless boosted_fields.empty?
|
53
|
+
params[:qf] = boosted_fields.map do |field|
|
54
|
+
field.to_boosted_field
|
55
|
+
end.join(' ')
|
56
|
+
end
|
57
|
+
params
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Sunspot
|
2
|
+
module Query
|
3
|
+
#
|
4
|
+
# A query component that holds information about pagination. Unlike other
|
5
|
+
# query components, this one is mutable, because the query itself holds a
|
6
|
+
# reference to it and updates it if pagination is changed.
|
7
|
+
#
|
8
|
+
class Pagination #:nodoc:
|
9
|
+
attr_reader :page, :per_page, :offset
|
10
|
+
|
11
|
+
def initialize(page = nil, per_page = nil, offset = nil)
|
12
|
+
self.offset, self.page, self.per_page = offset, page, per_page
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_params
|
16
|
+
{ :start => start, :rows => rows }
|
17
|
+
end
|
18
|
+
|
19
|
+
def page=(page)
|
20
|
+
@page = page.to_i if page
|
21
|
+
end
|
22
|
+
|
23
|
+
def per_page=(per_page)
|
24
|
+
@per_page = per_page.to_i if per_page
|
25
|
+
end
|
26
|
+
|
27
|
+
def offset=(offset)
|
28
|
+
@offset = offset.to_i
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def start
|
34
|
+
(@page - 1) * @per_page + @offset
|
35
|
+
end
|
36
|
+
|
37
|
+
def rows
|
38
|
+
@per_page
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,262 @@
|
|
1
|
+
module Sunspot
|
2
|
+
module Query
|
3
|
+
module Restriction #:nodoc:
|
4
|
+
class <<self
|
5
|
+
#
|
6
|
+
# Return the names of all of the restriction classes that should be made
|
7
|
+
# available to the DSL.
|
8
|
+
#
|
9
|
+
# ==== Returns
|
10
|
+
#
|
11
|
+
# Array:: Collection of restriction class names
|
12
|
+
#
|
13
|
+
def names
|
14
|
+
constants - %w(Base) #XXX this seems ugly
|
15
|
+
end
|
16
|
+
|
17
|
+
#
|
18
|
+
# Convenience method to access a restriction class by an underscored
|
19
|
+
# symbol or string
|
20
|
+
#
|
21
|
+
def [](restriction_name)
|
22
|
+
@types ||= {}
|
23
|
+
@types[restriction_name.to_sym] ||= const_get(Sunspot::Util.camel_case(restriction_name.to_s))
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
#
|
28
|
+
# Subclasses of this class represent restrictions that can be applied to
|
29
|
+
# a Sunspot query. The Sunspot::DSL::Restriction class presents a builder
|
30
|
+
# API for instances of this class.
|
31
|
+
#
|
32
|
+
# Implementations of this class must respond to #to_params and
|
33
|
+
# #to_negated_params. Instead of implementing those methods, they may
|
34
|
+
# choose to implement any of:
|
35
|
+
#
|
36
|
+
# * #to_positive_boolean_phrase, and optionally #to_negated_boolean_phrase
|
37
|
+
# * #to_solr_conditional
|
38
|
+
#
|
39
|
+
class Base #:nodoc:
|
40
|
+
include Filter
|
41
|
+
include RSolr::Char
|
42
|
+
|
43
|
+
RESERVED_WORDS = Set['AND', 'OR', 'NOT']
|
44
|
+
|
45
|
+
def initialize(negated, field, value)
|
46
|
+
raise ArgumentError.new("RFCTR") unless [true, false].include?(negated)
|
47
|
+
@negated, @field, @value = negated, field, value
|
48
|
+
end
|
49
|
+
|
50
|
+
#
|
51
|
+
# A hash representing this restriction in solr-ruby's parameter format.
|
52
|
+
# All restriction implementations must respond to this method; however,
|
53
|
+
# the base implementation delegates to the #to_positive_boolean_phrase method, so
|
54
|
+
# subclasses may (and probably should) choose to implement that method
|
55
|
+
# instead.
|
56
|
+
#
|
57
|
+
# ==== Returns
|
58
|
+
#
|
59
|
+
# Hash:: Representation of this restriction as solr-ruby parameters
|
60
|
+
#
|
61
|
+
def to_params
|
62
|
+
{ :fq => [to_filter_query] }
|
63
|
+
end
|
64
|
+
|
65
|
+
#
|
66
|
+
# Return the boolean phrase associated with this restriction object.
|
67
|
+
# Differentiates between positive and negated boolean phrases depending
|
68
|
+
# on whether this restriction is negated.
|
69
|
+
#
|
70
|
+
def to_boolean_phrase
|
71
|
+
unless negated?
|
72
|
+
to_positive_boolean_phrase
|
73
|
+
else
|
74
|
+
to_negated_boolean_phrase
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
#
|
79
|
+
# Boolean phrase representing this restriction in the positive. Subclasses
|
80
|
+
# may choose to implement this method rather than #to_params; however,
|
81
|
+
# this method delegates to the abstract #to_solr_conditional method, which
|
82
|
+
# in most cases will be what subclasses will want to implement.
|
83
|
+
# #to_solr_conditional contains the boolean phrase representing the
|
84
|
+
# condition but leaves out the field name (see built-in implementations
|
85
|
+
# for examples)
|
86
|
+
#
|
87
|
+
# ==== Returns
|
88
|
+
#
|
89
|
+
# String:: Boolean phrase for restriction in the positive
|
90
|
+
#
|
91
|
+
def to_positive_boolean_phrase
|
92
|
+
"#{escape(@field.indexed_name)}:#{to_solr_conditional}"
|
93
|
+
end
|
94
|
+
|
95
|
+
#
|
96
|
+
# Boolean phrase representing this restriction in the negated. Subclasses
|
97
|
+
# may choose to implement this method, but it is not necessary, as the
|
98
|
+
# base implementation delegates to #to_positive_boolean_phrase.
|
99
|
+
#
|
100
|
+
# ==== Returns
|
101
|
+
#
|
102
|
+
# String:: Boolean phrase for restriction in the negated
|
103
|
+
#
|
104
|
+
def to_negated_boolean_phrase
|
105
|
+
"-#{to_positive_boolean_phrase}"
|
106
|
+
end
|
107
|
+
|
108
|
+
#
|
109
|
+
# Whether this restriction should be negated from its original meaning
|
110
|
+
#
|
111
|
+
def negated? #:nodoc:
|
112
|
+
!!@negated
|
113
|
+
end
|
114
|
+
|
115
|
+
#
|
116
|
+
# Return a new restriction that is the negated version of this one. It
|
117
|
+
# is used by disjunction denormalization.
|
118
|
+
#
|
119
|
+
def negate
|
120
|
+
self.class.new(!@negated, @field, @value)
|
121
|
+
end
|
122
|
+
|
123
|
+
protected
|
124
|
+
|
125
|
+
#
|
126
|
+
# Return escaped Solr API representation of given value
|
127
|
+
#
|
128
|
+
# ==== Parameters
|
129
|
+
#
|
130
|
+
# value<Object>::
|
131
|
+
# value to convert to Solr representation (default: @value)
|
132
|
+
#
|
133
|
+
# ==== Returns
|
134
|
+
#
|
135
|
+
# String:: Solr API representation of given value
|
136
|
+
#
|
137
|
+
def solr_value(value = @value)
|
138
|
+
solr_value = escape(@field.to_indexed(value))
|
139
|
+
if RESERVED_WORDS.include?(solr_value)
|
140
|
+
%Q("#{solr_value}")
|
141
|
+
else
|
142
|
+
solr_value
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
#
|
148
|
+
# Results must have field with value equal to given value. If the value
|
149
|
+
# is nil, results must have no value for the given field.
|
150
|
+
#
|
151
|
+
class EqualTo < Base
|
152
|
+
def to_positive_boolean_phrase
|
153
|
+
unless @value.nil?
|
154
|
+
super
|
155
|
+
else
|
156
|
+
"#{escape(@field.indexed_name)}:[* TO *]"
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def negated?
|
161
|
+
if @value.nil?
|
162
|
+
!super
|
163
|
+
else
|
164
|
+
super
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
private
|
169
|
+
|
170
|
+
def to_solr_conditional
|
171
|
+
"#{solr_value}"
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
#
|
176
|
+
# Results must have field with value less than given value
|
177
|
+
#
|
178
|
+
class LessThan < Base
|
179
|
+
private
|
180
|
+
|
181
|
+
def solr_value(value = @value)
|
182
|
+
solr_value = super
|
183
|
+
solr_value = "\"#{solr_value}\"" if solr_value.index(' ')
|
184
|
+
solr_value
|
185
|
+
end
|
186
|
+
|
187
|
+
def to_solr_conditional
|
188
|
+
"[* TO #{solr_value}]"
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
#
|
193
|
+
# Results must have field with value greater than given value
|
194
|
+
#
|
195
|
+
class GreaterThan < Base
|
196
|
+
private
|
197
|
+
|
198
|
+
def solr_value(value = @value)
|
199
|
+
solr_value = super
|
200
|
+
solr_value = "\"#{solr_value}\"" if solr_value.index(' ')
|
201
|
+
solr_value
|
202
|
+
end
|
203
|
+
|
204
|
+
def to_solr_conditional
|
205
|
+
"[#{solr_value} TO *]"
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
#
|
210
|
+
# Results must have field with value in given range
|
211
|
+
#
|
212
|
+
class Between < Base
|
213
|
+
private
|
214
|
+
|
215
|
+
def solr_value(value = @value)
|
216
|
+
solr_value = super
|
217
|
+
solr_value = "\"#{solr_value}\"" if solr_value.index(' ')
|
218
|
+
solr_value
|
219
|
+
end
|
220
|
+
|
221
|
+
def to_solr_conditional
|
222
|
+
"[#{solr_value(@value.first)} TO #{solr_value(@value.last)}]"
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
#
|
227
|
+
# Results must have field with value included in given collection
|
228
|
+
#
|
229
|
+
class AnyOf < Base
|
230
|
+
private
|
231
|
+
|
232
|
+
def to_solr_conditional
|
233
|
+
"(#{@value.map { |v| solr_value v } * ' OR '})"
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
#
|
238
|
+
# Results must have field with values matching all values in given
|
239
|
+
# collection (only makes sense for fields with multiple values)
|
240
|
+
#
|
241
|
+
class AllOf < Base
|
242
|
+
private
|
243
|
+
|
244
|
+
def to_solr_conditional
|
245
|
+
"(#{@value.map { |v| solr_value v } * ' AND '})"
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
#
|
250
|
+
# Results must have a field with a value that begins with the argument.
|
251
|
+
# Most useful for strings, but in theory will work with anything.
|
252
|
+
#
|
253
|
+
class StartingWith < Base
|
254
|
+
private
|
255
|
+
|
256
|
+
def to_solr_conditional
|
257
|
+
"#{solr_value(@value)}*"
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
262
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
module Sunspot
|
2
|
+
module Query
|
3
|
+
#
|
4
|
+
# The classes in this module implement query components that build sort
|
5
|
+
# parameters for Solr. As well as regular sort on fields, there are several
|
6
|
+
# "special" sorts that allow ordering for metrics calculated during the
|
7
|
+
# search.
|
8
|
+
#
|
9
|
+
module Sort #:nodoc: all
|
10
|
+
DIRECTIONS = {
|
11
|
+
:asc => 'asc',
|
12
|
+
:ascending => 'asc',
|
13
|
+
:desc => 'desc',
|
14
|
+
:descending => 'desc'
|
15
|
+
}
|
16
|
+
|
17
|
+
class <<self
|
18
|
+
#
|
19
|
+
# Certain field names are "special", referring to specific non-field
|
20
|
+
# sorts, which are generally by other metrics associated with hits.
|
21
|
+
#
|
22
|
+
# XXX I'm not entirely convinced it's a good idea to prevent anyone from
|
23
|
+
# ever sorting by a field named 'score', etc.
|
24
|
+
#
|
25
|
+
def special(name)
|
26
|
+
special_class_name = "#{Util.camel_case(name.to_s)}Sort"
|
27
|
+
if const_defined?(special_class_name) && special_class_name != 'FieldSort'
|
28
|
+
const_get(special_class_name)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
#
|
34
|
+
# Base class for sorts. All subclasses should implement the #to_param
|
35
|
+
# method, which is a string that is then concatenated with other sort
|
36
|
+
# strings by the SortComposite to form the sort parameter.
|
37
|
+
#
|
38
|
+
class Abstract
|
39
|
+
def initialize(direction)
|
40
|
+
@direction = (direction || :asc).to_sym
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
#
|
46
|
+
# Translate fairly forgiving direction argument into solr direction
|
47
|
+
#
|
48
|
+
def direction_for_solr
|
49
|
+
DIRECTIONS[@direction] ||
|
50
|
+
raise(
|
51
|
+
ArgumentError,
|
52
|
+
"Unknown sort direction #{@direction}. Acceptable input is: #{DIRECTIONS.keys.map { |input| input.inspect } * ', '}"
|
53
|
+
)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
#
|
58
|
+
# A FieldSort is the usual kind of sort, by the value of a particular
|
59
|
+
# field, ascending or descending
|
60
|
+
#
|
61
|
+
class FieldSort < Abstract
|
62
|
+
def initialize(field, direction = nil)
|
63
|
+
if field.multiple?
|
64
|
+
raise(ArgumentError, "#{field.name} cannot be used for ordering because it is a multiple-value field")
|
65
|
+
end
|
66
|
+
@field, @direction = field, (direction || :asc).to_sym
|
67
|
+
end
|
68
|
+
|
69
|
+
def to_param
|
70
|
+
"#{@field.indexed_name.to_sym} #{direction_for_solr}"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
#
|
75
|
+
# A RandomSort uses Solr's random field functionality to sort results
|
76
|
+
# (usually) randomly.
|
77
|
+
#
|
78
|
+
class RandomSort < Abstract
|
79
|
+
def to_param
|
80
|
+
"random_#{rand(1<<16)} #{direction_for_solr}"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
#
|
85
|
+
# A ScoreSort sorts by keyword relevance score. This is only useful when
|
86
|
+
# performing fulltext search.
|
87
|
+
#
|
88
|
+
class ScoreSort < Abstract
|
89
|
+
def to_param
|
90
|
+
"score #{direction_for_solr}"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
#
|
95
|
+
# A GeodistSort sorts by distance from a given point.
|
96
|
+
#
|
97
|
+
class GeodistSort < FieldSort
|
98
|
+
def initialize(field, lat, lon, direction)
|
99
|
+
@lat, @lon = lat, lon
|
100
|
+
super(field, direction)
|
101
|
+
end
|
102
|
+
|
103
|
+
def to_param
|
104
|
+
"geodist(#{@field.indexed_name.to_sym},#{@lat},#{@lon}) #{direction_for_solr}"
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Sunspot
|
2
|
+
module Query
|
3
|
+
#
|
4
|
+
# The SortComposite class encapsulates an ordered collection of Sort
|
5
|
+
# objects. It's necessary to keep this as a separate class as Solr takes
|
6
|
+
# the sort as a single parameter, so adding sorts as regular components
|
7
|
+
# would not merge correctly in the #to_params method.
|
8
|
+
#
|
9
|
+
class SortComposite #:nodoc:
|
10
|
+
def initialize
|
11
|
+
@sorts = []
|
12
|
+
end
|
13
|
+
|
14
|
+
#
|
15
|
+
# Add a sort to the composite
|
16
|
+
#
|
17
|
+
def <<(sort)
|
18
|
+
@sorts << sort
|
19
|
+
end
|
20
|
+
|
21
|
+
#
|
22
|
+
# Combine the sorts into a single param by joining them
|
23
|
+
#
|
24
|
+
def to_params(prefix = "")
|
25
|
+
unless @sorts.empty?
|
26
|
+
key = "#{prefix}sort".to_sym
|
27
|
+
{ key => @sorts.map { |sort| sort.to_param } * ', ' }
|
28
|
+
else
|
29
|
+
{}
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Sunspot
|
2
|
+
module Query
|
3
|
+
class StandardQuery < CommonQuery
|
4
|
+
attr_accessor :scope, :fulltext
|
5
|
+
|
6
|
+
def initialize(types)
|
7
|
+
super
|
8
|
+
@components << @fulltext = CompositeFulltext.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def add_fulltext(keywords)
|
12
|
+
@fulltext.add(keywords)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Sunspot
|
2
|
+
module Query
|
3
|
+
class TextFieldBoost #:nodoc:
|
4
|
+
attr_reader :boost
|
5
|
+
|
6
|
+
def initialize(field, boost = nil)
|
7
|
+
@field, @boost = field, boost
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_boosted_field
|
11
|
+
boosted_field = @field.indexed_name
|
12
|
+
boosted_field.concat("^#{@boost}") if @boost
|
13
|
+
boosted_field
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
%w(filter abstract_field_facet connective boost_query date_field_facet dismax
|
2
|
+
field_facet highlighting pagination restriction common_query
|
3
|
+
standard_query more_like_this more_like_this_query geo geofilt bbox query_facet
|
4
|
+
scope sort sort_composite text_field_boost function_query
|
5
|
+
composite_fulltext field_group).each do |file|
|
6
|
+
require(File.join(File.dirname(__FILE__), 'query', file))
|
7
|
+
end
|
8
|
+
module Sunspot
|
9
|
+
module Query #:nodoc:all
|
10
|
+
end
|
11
|
+
end
|