sunspot_rbg 1.3.0
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 +4 -0
- data/History.txt +222 -0
- data/LICENSE +18 -0
- data/Rakefile +17 -0
- data/TODO +13 -0
- data/VERSION.yml +4 -0
- data/bin/sunspot-installer +19 -0
- data/bin/sunspot-solr +74 -0
- data/installer/config/schema.yml +95 -0
- data/lib/light_config.rb +40 -0
- data/lib/sunspot/adapters.rb +265 -0
- data/lib/sunspot/composite_setup.rb +202 -0
- data/lib/sunspot/configuration.rb +46 -0
- data/lib/sunspot/data_extractor.rb +50 -0
- data/lib/sunspot/dsl/adjustable.rb +47 -0
- data/lib/sunspot/dsl/field_query.rb +279 -0
- data/lib/sunspot/dsl/fields.rb +103 -0
- data/lib/sunspot/dsl/fulltext.rb +243 -0
- data/lib/sunspot/dsl/function.rb +14 -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 +28 -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 +121 -0
- data/lib/sunspot/dsl/scope.rb +217 -0
- data/lib/sunspot/dsl/search.rb +30 -0
- data/lib/sunspot/dsl/standard_query.rb +121 -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 +131 -0
- data/lib/sunspot/installer/library_installer.rb +45 -0
- data/lib/sunspot/installer/schema_builder.rb +219 -0
- data/lib/sunspot/installer/solrconfig_updater.rb +76 -0
- data/lib/sunspot/installer/task_helper.rb +18 -0
- data/lib/sunspot/installer.rb +31 -0
- data/lib/sunspot/query/abstract_field_facet.rb +52 -0
- data/lib/sunspot/query/boost_query.rb +24 -0
- data/lib/sunspot/query/common_query.rb +85 -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 +128 -0
- data/lib/sunspot/query/field_facet.rb +41 -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/highlighting.rb +55 -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 +38 -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 +95 -0
- data/lib/sunspot/query/sort_composite.rb +33 -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 +293 -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/highlight.rb +38 -0
- data/lib/sunspot/search/hit.rb +136 -0
- data/lib/sunspot/search/more_like_this_search.rb +31 -0
- data/lib/sunspot/search/paginated_collection.rb +55 -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/server.rb +152 -0
- data/lib/sunspot/session.rb +260 -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/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 +87 -0
- data/lib/sunspot/setup.rb +350 -0
- data/lib/sunspot/text_field_setup.rb +29 -0
- data/lib/sunspot/type.rb +372 -0
- data/lib/sunspot/util.rb +243 -0
- data/lib/sunspot/version.rb +3 -0
- data/lib/sunspot.rb +569 -0
- data/lib/sunspot_rbg.rb +7 -0
- data/log/.gitignore +1 -0
- data/pkg/.gitignore +1 -0
- data/script/console +10 -0
- data/solr/README.txt +42 -0
- data/solr/etc/jetty.xml +218 -0
- data/solr/etc/webdefault.xml +379 -0
- data/solr/lib/jetty-6.1.3.jar +0 -0
- data/solr/lib/jetty-util-6.1.3.jar +0 -0
- data/solr/lib/jsp-2.1/ant-1.6.5.jar +0 -0
- data/solr/lib/jsp-2.1/core-3.1.1.jar +0 -0
- data/solr/lib/jsp-2.1/jsp-2.1.jar +0 -0
- data/solr/lib/jsp-2.1/jsp-api-2.1.jar +0 -0
- data/solr/lib/servlet-api-2.5-6.1.3.jar +0 -0
- data/solr/logs/.gitignore +1 -0
- data/solr/solr/.gitignore +1 -0
- data/solr/solr/README.txt +54 -0
- data/solr/solr/conf/admin-extra.html +31 -0
- data/solr/solr/conf/elevate.xml +36 -0
- data/solr/solr/conf/mapping-ISOLatin1Accent.txt +246 -0
- data/solr/solr/conf/protwords.txt +21 -0
- data/solr/solr/conf/schema.xml +238 -0
- data/solr/solr/conf/scripts.conf +24 -0
- data/solr/solr/conf/solrconfig.xml +934 -0
- data/solr/solr/conf/spellings.txt +2 -0
- data/solr/solr/conf/stopwords.txt +58 -0
- data/solr/solr/conf/synonyms.txt +31 -0
- data/solr/solr/conf/xslt/example.xsl +132 -0
- data/solr/solr/conf/xslt/example_atom.xsl +67 -0
- data/solr/solr/conf/xslt/example_rss.xsl +66 -0
- data/solr/solr/conf/xslt/luke.xsl +337 -0
- data/solr/start.jar +0 -0
- data/solr/webapps/solr.war +0 -0
- data/solr-1.3/etc/jetty.xml +212 -0
- data/solr-1.3/etc/webdefault.xml +379 -0
- data/solr-1.3/lib/jetty-6.1.3.jar +0 -0
- data/solr-1.3/lib/jetty-util-6.1.3.jar +0 -0
- data/solr-1.3/lib/jsp-2.1/ant-1.6.5.jar +0 -0
- data/solr-1.3/lib/jsp-2.1/core-3.1.1.jar +0 -0
- data/solr-1.3/lib/jsp-2.1/jsp-2.1.jar +0 -0
- data/solr-1.3/lib/jsp-2.1/jsp-api-2.1.jar +0 -0
- data/solr-1.3/lib/servlet-api-2.5-6.1.3.jar +0 -0
- data/solr-1.3/solr/conf/elevate.xml +36 -0
- data/solr-1.3/solr/conf/protwords.txt +21 -0
- data/solr-1.3/solr/conf/schema.xml +64 -0
- data/solr-1.3/solr/conf/solrconfig.xml +725 -0
- data/solr-1.3/solr/conf/stopwords.txt +57 -0
- data/solr-1.3/solr/conf/synonyms.txt +31 -0
- data/solr-1.3/solr/lib/geoapi-nogenerics-2.1-M2.jar +0 -0
- data/solr-1.3/solr/lib/gt2-referencing-2.3.1.jar +0 -0
- data/solr-1.3/solr/lib/jsr108-0.01.jar +0 -0
- data/solr-1.3/solr/lib/locallucene.jar +0 -0
- data/solr-1.3/solr/lib/localsolr.jar +0 -0
- data/solr-1.3/start.jar +0 -0
- data/solr-1.3/webapps/solr.war +0 -0
- data/spec/api/adapters_spec.rb +33 -0
- data/spec/api/binding_spec.rb +50 -0
- data/spec/api/indexer/attributes_spec.rb +149 -0
- data/spec/api/indexer/batch_spec.rb +46 -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 +70 -0
- data/spec/api/query/geo_examples.rb +68 -0
- data/spec/api/query/highlighting_examples.rb +223 -0
- data/spec/api/query/more_like_this_spec.rb +140 -0
- data/spec/api/query/ordering_pagination_examples.rb +95 -0
- data/spec/api/query/scope_examples.rb +275 -0
- data/spec/api/query/spec_helper.rb +1 -0
- data/spec/api/query/standard_spec.rb +28 -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 +120 -0
- data/spec/api/search/paginated_collection_spec.rb +26 -0
- data/spec/api/search/results_spec.rb +66 -0
- data/spec/api/search/search_spec.rb +23 -0
- data/spec/api/search/spec_helper.rb +1 -0
- data/spec/api/server_spec.rb +91 -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 +50 -0
- data/spec/api/session_spec.rb +220 -0
- data/spec/api/spec_helper.rb +3 -0
- data/spec/api/sunspot_spec.rb +18 -0
- data/spec/ext.rb +11 -0
- data/spec/helpers/indexer_helper.rb +29 -0
- data/spec/helpers/query_helper.rb +38 -0
- data/spec/helpers/search_helper.rb +80 -0
- data/spec/integration/dynamic_fields_spec.rb +55 -0
- data/spec/integration/faceting_spec.rb +238 -0
- data/spec/integration/highlighting_spec.rb +22 -0
- data/spec/integration/indexing_spec.rb +33 -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/spec_helper.rb +7 -0
- data/spec/integration/stored_fields_spec.rb +10 -0
- data/spec/integration/test_pagination.rb +32 -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 +85 -0
- data/spec/mocks/super_class.rb +2 -0
- data/spec/mocks/user.rb +13 -0
- data/spec/spec_helper.rb +30 -0
- data/sunspot.gemspec +40 -0
- data/tasks/rdoc.rake +27 -0
- data/tasks/schema.rake +19 -0
- data/tasks/todo.rake +4 -0
- metadata +457 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
module Sunspot
|
|
2
|
+
module Search
|
|
3
|
+
#
|
|
4
|
+
# A FieldFacet is a facet whose rows are all values for a certain field, in
|
|
5
|
+
# contrast to a QueryFacet, whose rows represent arbitrary queries.
|
|
6
|
+
#
|
|
7
|
+
class FieldFacet < QueryFacet
|
|
8
|
+
def initialize(field, search, options) #:nodoc:
|
|
9
|
+
super((options[:name] || field.name).to_sym, search, options)
|
|
10
|
+
@field = field
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def field_name
|
|
14
|
+
@field.name
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
#
|
|
18
|
+
# Get the rows returned for this facet.
|
|
19
|
+
#
|
|
20
|
+
# ==== Options (options)
|
|
21
|
+
#
|
|
22
|
+
# :verify::
|
|
23
|
+
# Only return rows for which the referenced object exists in the data
|
|
24
|
+
# store. This option is ignored unless the field associated with this
|
|
25
|
+
# facet is configured with a :references argument.
|
|
26
|
+
#
|
|
27
|
+
# ==== Returns
|
|
28
|
+
#
|
|
29
|
+
# Array:: Array of FacetRow objects
|
|
30
|
+
#
|
|
31
|
+
def rows(options = {})
|
|
32
|
+
if options[:verify]
|
|
33
|
+
verified_rows
|
|
34
|
+
else
|
|
35
|
+
@rows ||=
|
|
36
|
+
begin
|
|
37
|
+
rows = super
|
|
38
|
+
has_query_facets = !rows.empty?
|
|
39
|
+
if @search.facet_response['facet_fields']
|
|
40
|
+
if data = @search.facet_response['facet_fields'][key]
|
|
41
|
+
data.each_slice(2) do |value, count|
|
|
42
|
+
row = FacetRow.new(@field.cast(value), count, self)
|
|
43
|
+
rows << row
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
sort_rows!(rows) if has_query_facets
|
|
48
|
+
rows
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
#
|
|
54
|
+
# If this facet references a model class, populate the rows with instances
|
|
55
|
+
# of the model class by loading them out of the appropriate adapter.
|
|
56
|
+
#
|
|
57
|
+
def populate_instances #:nodoc:
|
|
58
|
+
if reference = @field.reference
|
|
59
|
+
values_hash = rows.inject({}) do |hash, row|
|
|
60
|
+
hash[row.value] = row
|
|
61
|
+
hash
|
|
62
|
+
end
|
|
63
|
+
instances = Adapters::DataAccessor.create(Sunspot::Util.full_const_get(reference)).load_all(
|
|
64
|
+
values_hash.keys
|
|
65
|
+
)
|
|
66
|
+
instances.each do |instance|
|
|
67
|
+
values_hash[Adapters::InstanceAdapter.adapt(instance).id].instance = instance
|
|
68
|
+
end
|
|
69
|
+
true
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
private
|
|
74
|
+
|
|
75
|
+
def verified_rows
|
|
76
|
+
if @field.reference
|
|
77
|
+
@verified_rows ||= rows.select { |row| row.instance }
|
|
78
|
+
else
|
|
79
|
+
rows
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def key
|
|
84
|
+
@key ||= (@options[:name] || @field.indexed_name).to_s
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
module Sunspot
|
|
2
|
+
module Search
|
|
3
|
+
#
|
|
4
|
+
# A Highlight represents a single highlighted fragment of text from a
|
|
5
|
+
# document. Depending on the highlighting parameters used for search, there
|
|
6
|
+
# may be more than one Highlight object for a given field in a given result.
|
|
7
|
+
#
|
|
8
|
+
class Highlight
|
|
9
|
+
HIGHLIGHT_MATCHER = /@@@hl@@@(.*?)@@@endhl@@@/ #:nodoc:
|
|
10
|
+
|
|
11
|
+
#
|
|
12
|
+
# The name of the field in which the highlight appeared.
|
|
13
|
+
#
|
|
14
|
+
attr_reader :field_name
|
|
15
|
+
|
|
16
|
+
def initialize(field_name, highlight) #:nodoc:
|
|
17
|
+
@field_name = field_name.to_sym
|
|
18
|
+
@highlight = highlight.to_s.strip
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
#
|
|
22
|
+
# Returns the highlighted text with formatting according to the template given in &block.
|
|
23
|
+
# When no block is given, <em> and </em> are used to surround the highlight.
|
|
24
|
+
#
|
|
25
|
+
# ==== Example
|
|
26
|
+
#
|
|
27
|
+
# search.highlights(:body).first.format { |word| "<strong>#{word}</strong>" }
|
|
28
|
+
#
|
|
29
|
+
def format(&block)
|
|
30
|
+
block ||= proc { |word| "<em>#{word}</em>" }
|
|
31
|
+
@highlight.gsub(HIGHLIGHT_MATCHER) do
|
|
32
|
+
block.call(Regexp.last_match[1])
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
alias_method :formatted, :format
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
module Sunspot
|
|
2
|
+
module Search
|
|
3
|
+
#
|
|
4
|
+
# Hit objects represent the raw information returned by Solr for a single
|
|
5
|
+
# document. As well as the primary key and class name, hit objects give
|
|
6
|
+
# access to stored field values, keyword relevance score, and keyword
|
|
7
|
+
# highlighting.
|
|
8
|
+
#
|
|
9
|
+
class Hit
|
|
10
|
+
SPECIAL_KEYS = Set.new(%w(id type score)) #:nodoc:
|
|
11
|
+
|
|
12
|
+
#
|
|
13
|
+
# Primary key of object associated with this hit, as string.
|
|
14
|
+
#
|
|
15
|
+
attr_reader :primary_key
|
|
16
|
+
#
|
|
17
|
+
# Class name of object associated with this hit, as string.
|
|
18
|
+
#
|
|
19
|
+
attr_reader :class_name
|
|
20
|
+
#
|
|
21
|
+
# Keyword relevance score associated with this result. Nil if this hit
|
|
22
|
+
# is not from a keyword search.
|
|
23
|
+
#
|
|
24
|
+
attr_reader :score
|
|
25
|
+
#
|
|
26
|
+
|
|
27
|
+
attr_writer :result #:nodoc:
|
|
28
|
+
|
|
29
|
+
def initialize(raw_hit, highlights, search) #:nodoc:
|
|
30
|
+
@class_name, @primary_key = *raw_hit['id'].match(/([^ ]+) (.+)/)[1..2]
|
|
31
|
+
@score = raw_hit['score']
|
|
32
|
+
@search = search
|
|
33
|
+
@stored_values = raw_hit
|
|
34
|
+
@stored_cache = {}
|
|
35
|
+
@highlights = highlights
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
#
|
|
39
|
+
# Returns all highlights for this hit when called without parameters.
|
|
40
|
+
# When a field_name is provided, returns only the highlight for this field.
|
|
41
|
+
#
|
|
42
|
+
def highlights(field_name = nil)
|
|
43
|
+
if field_name.nil?
|
|
44
|
+
highlights_cache.values.flatten
|
|
45
|
+
else
|
|
46
|
+
highlights_cache[field_name.to_sym]
|
|
47
|
+
end || []
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
#
|
|
51
|
+
# Return the first highlight found for a given field, or nil if there is
|
|
52
|
+
# none.
|
|
53
|
+
#
|
|
54
|
+
def highlight(field_name)
|
|
55
|
+
highlights(field_name).first
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
#
|
|
59
|
+
# Retrieve stored field value. For any attribute field configured with
|
|
60
|
+
# :stored => true, the Hit object will contain the stored value for
|
|
61
|
+
# that field. The value of this field will be typecast according to the
|
|
62
|
+
# type of the field.
|
|
63
|
+
#
|
|
64
|
+
# ==== Parameters
|
|
65
|
+
#
|
|
66
|
+
# field_name<Symbol>::
|
|
67
|
+
# The name of the field for which to retrieve the stored value.
|
|
68
|
+
# dynamic_field_name<Symbol>::
|
|
69
|
+
# If you want to access a stored dynamic field, this should be the
|
|
70
|
+
# dynamic component of the field name.
|
|
71
|
+
#
|
|
72
|
+
def stored(field_name, dynamic_field_name = nil)
|
|
73
|
+
field_key =
|
|
74
|
+
if dynamic_field_name
|
|
75
|
+
[field_name.to_sym, dynamic_field_name.to_sym]
|
|
76
|
+
else
|
|
77
|
+
field_name.to_sym
|
|
78
|
+
end
|
|
79
|
+
return @stored_cache[field_key] if @stored_cache.has_key?(field_key)
|
|
80
|
+
@stored_cache[field_key] = stored_value(field_name, dynamic_field_name)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
#
|
|
84
|
+
# Retrieve the instance associated with this hit. This is lazy-loaded, but
|
|
85
|
+
# the first time it is called on any hit, all the hits for the search will
|
|
86
|
+
# load their instances using the adapter's #load_all method.
|
|
87
|
+
#
|
|
88
|
+
def result
|
|
89
|
+
return @result if defined?(@result)
|
|
90
|
+
@search.populate_hits
|
|
91
|
+
@result
|
|
92
|
+
end
|
|
93
|
+
alias_method :instance, :result
|
|
94
|
+
|
|
95
|
+
def inspect #:nodoc:
|
|
96
|
+
"#<Sunspot::Search::Hit:#{@class_name} #{@primary_key}>"
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
private
|
|
100
|
+
|
|
101
|
+
def setup
|
|
102
|
+
@setup ||= Sunspot::Setup.for(Util.full_const_get(@class_name))
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def highlights_cache
|
|
106
|
+
@highlights_cache ||=
|
|
107
|
+
begin
|
|
108
|
+
cache = {}
|
|
109
|
+
if @highlights
|
|
110
|
+
@highlights.each_pair do |indexed_field_name, highlight_strings|
|
|
111
|
+
field_name = indexed_field_name.sub(/_[a-z]+$/, '').to_sym
|
|
112
|
+
cache[field_name] = highlight_strings.map do |highlight_string|
|
|
113
|
+
Highlight.new(field_name, highlight_string)
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
cache
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def stored_value(field_name, dynamic_field_name)
|
|
122
|
+
setup.stored_fields(field_name, dynamic_field_name).each do |field|
|
|
123
|
+
if value = @stored_values[field.indexed_name]
|
|
124
|
+
case value
|
|
125
|
+
when Array
|
|
126
|
+
return value.map { |item| field.cast(item) }
|
|
127
|
+
else
|
|
128
|
+
return field.cast(value)
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
nil
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module Sunspot
|
|
2
|
+
#
|
|
3
|
+
# This class encapsulates the results of a Solr MoreLikeThis search. It provides access
|
|
4
|
+
# to search results, total result count, and pagination information.
|
|
5
|
+
# Instances of MoreLikeThis are returned by the Sunspot.more_like_this and
|
|
6
|
+
# Sunspot.new_more_like_this methods.
|
|
7
|
+
#
|
|
8
|
+
module Search
|
|
9
|
+
class MoreLikeThisSearch < AbstractSearch
|
|
10
|
+
def execute
|
|
11
|
+
if @query.more_like_this.fields.empty?
|
|
12
|
+
@setup.all_more_like_this_fields.each do |field|
|
|
13
|
+
@query.more_like_this.add_field(field)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
super
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def request_handler
|
|
20
|
+
super || :mlt
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
# override
|
|
26
|
+
def dsl
|
|
27
|
+
DSL::MoreLikeThisQuery.new(self, @query, @setup)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
module Sunspot
|
|
2
|
+
module Search
|
|
3
|
+
|
|
4
|
+
class PaginatedCollection
|
|
5
|
+
instance_methods.each { |m| undef_method m unless m =~ /^__|instance_eval|object_id/ }
|
|
6
|
+
|
|
7
|
+
attr_reader :total_count, :current_page, :per_page
|
|
8
|
+
alias :total_entries :total_count
|
|
9
|
+
alias :limit_value :per_page
|
|
10
|
+
|
|
11
|
+
def initialize(collection, page, per_page, total)
|
|
12
|
+
@collection = collection
|
|
13
|
+
@current_page = page
|
|
14
|
+
@per_page = per_page
|
|
15
|
+
@total_count = total
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def total_pages
|
|
19
|
+
(total_count.to_f / per_page).ceil
|
|
20
|
+
end
|
|
21
|
+
alias :num_pages :total_pages
|
|
22
|
+
|
|
23
|
+
def first_page?
|
|
24
|
+
current_page == 1
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def last_page?
|
|
28
|
+
current_page >= total_pages
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def previous_page
|
|
32
|
+
current_page > 1 ? (current_page - 1) : nil
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def next_page
|
|
36
|
+
current_page < total_pages ? (current_page + 1) : nil
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def out_of_bounds?
|
|
40
|
+
current_page > total_pages
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def offset
|
|
44
|
+
(current_page - 1) * per_page
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def method_missing(method, *args, &block)
|
|
50
|
+
@collection.send(method, *args, &block)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
module Sunspot
|
|
2
|
+
module Search
|
|
3
|
+
class QueryFacet
|
|
4
|
+
RequestedFacet = Struct.new(:label, :boolean_phrase) #:nodoc:
|
|
5
|
+
|
|
6
|
+
attr_reader :name
|
|
7
|
+
|
|
8
|
+
def initialize(name, search, options) #:nodoc:
|
|
9
|
+
@name, @search, @options = name, search, options
|
|
10
|
+
@requested_facets = []
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def rows(options = {})
|
|
14
|
+
@rows ||=
|
|
15
|
+
begin
|
|
16
|
+
data = @search.facet_response['facet_queries']
|
|
17
|
+
rows = []
|
|
18
|
+
minimum_count =
|
|
19
|
+
case
|
|
20
|
+
when @options[:minimum_count] then @options[:minimum_count]
|
|
21
|
+
when @options[:zeros] then 0
|
|
22
|
+
else 1
|
|
23
|
+
end
|
|
24
|
+
@requested_facets.each do |requested_facet|
|
|
25
|
+
count = data[requested_facet.boolean_phrase] || 0
|
|
26
|
+
if count >= minimum_count
|
|
27
|
+
rows << FacetRow.new(requested_facet.label, count, self)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
sort_rows!(rows)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def add_row(label, boolean_phrase) #:nodoc:
|
|
35
|
+
@requested_facets << RequestedFacet.new(label, boolean_phrase)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def sort_rows!(rows)
|
|
41
|
+
case @options[:sort] || (:count if limit)
|
|
42
|
+
when :count
|
|
43
|
+
rows.sort! { |lrow, rrow| rrow.count <=> lrow.count }
|
|
44
|
+
when :index
|
|
45
|
+
rows.sort! do |lrow, rrow|
|
|
46
|
+
if lrow.respond_to?(:<=>)
|
|
47
|
+
lrow.value <=> rrow.value
|
|
48
|
+
elsif lrow.respond_to?(:first) && rrow.respond_to?(:first) && lrow.first.respond_to?(:<=>)
|
|
49
|
+
lrow.first.value <=> rrow.first.value
|
|
50
|
+
else
|
|
51
|
+
lrow.value.to_s <=> rrow.value.to_s
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
if limit
|
|
56
|
+
rows.replace(rows.first(limit))
|
|
57
|
+
end
|
|
58
|
+
rows
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def limit
|
|
62
|
+
return @limit if defined?(@limit)
|
|
63
|
+
@limit = (@options[:limit].to_i if @options[:limit].to_i > 0)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module Sunspot
|
|
2
|
+
module Search
|
|
3
|
+
#
|
|
4
|
+
# This class encapsulates the results of a Solr search. It provides access
|
|
5
|
+
# to search results, total result count, facets, and pagination information.
|
|
6
|
+
# Instances of Search are returned by the Sunspot.search and
|
|
7
|
+
# Sunspot.new_search methods.
|
|
8
|
+
#
|
|
9
|
+
class StandardSearch < AbstractSearch
|
|
10
|
+
def request_handler
|
|
11
|
+
super || :select
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def dsl
|
|
17
|
+
DSL::Search.new(self, @setup)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
require 'escape'
|
|
2
|
+
require 'set'
|
|
3
|
+
require 'tempfile'
|
|
4
|
+
|
|
5
|
+
module Sunspot
|
|
6
|
+
class Server #:nodoc:
|
|
7
|
+
# Raised if #stop is called but the server is not running
|
|
8
|
+
ServerError = Class.new(RuntimeError)
|
|
9
|
+
AlreadyRunningError = Class.new(ServerError)
|
|
10
|
+
NotRunningError = Class.new(ServerError)
|
|
11
|
+
|
|
12
|
+
# Name of the sunspot executable (shell script)
|
|
13
|
+
SOLR_START_JAR = File.expand_path(
|
|
14
|
+
File.join(File.dirname(__FILE__), '..', '..', 'solr', 'start.jar')
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
LOG_LEVELS = Set['SEVERE', 'WARNING', 'INFO', 'CONFIG', 'FINE', 'FINER', 'FINEST']
|
|
18
|
+
|
|
19
|
+
attr_accessor :min_memory, :max_memory, :port, :solr_data_dir, :solr_home, :log_file
|
|
20
|
+
attr_writer :pid_dir, :pid_file, :log_level, :solr_data_dir, :solr_home, :solr_jar
|
|
21
|
+
|
|
22
|
+
#
|
|
23
|
+
# Start the sunspot-solr server. Bootstrap solr_home first,
|
|
24
|
+
# if neccessary.
|
|
25
|
+
#
|
|
26
|
+
# ==== Returns
|
|
27
|
+
#
|
|
28
|
+
# Boolean:: success
|
|
29
|
+
#
|
|
30
|
+
def start
|
|
31
|
+
if File.exist?(pid_path)
|
|
32
|
+
existing_pid = IO.read(pid_path).to_i
|
|
33
|
+
begin
|
|
34
|
+
Process.kill(0, existing_pid)
|
|
35
|
+
raise(AlreadyRunningError, "Server is already running with PID #{existing_pid}")
|
|
36
|
+
rescue Errno::ESRCH
|
|
37
|
+
STDERR.puts("Removing stale PID file at #{pid_path}")
|
|
38
|
+
FileUtils.rm(pid_path)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
fork do
|
|
42
|
+
pid = fork do
|
|
43
|
+
Process.setsid
|
|
44
|
+
STDIN.reopen('/dev/null')
|
|
45
|
+
STDOUT.reopen('/dev/null', 'a')
|
|
46
|
+
STDERR.reopen(STDOUT)
|
|
47
|
+
run
|
|
48
|
+
end
|
|
49
|
+
FileUtils.mkdir_p(pid_dir)
|
|
50
|
+
File.open(pid_path, 'w') do |file|
|
|
51
|
+
file << pid
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
#
|
|
57
|
+
# Run the sunspot-solr server in the foreground. Boostrap
|
|
58
|
+
# solr_home first, if neccessary.
|
|
59
|
+
#
|
|
60
|
+
# ==== Returns
|
|
61
|
+
#
|
|
62
|
+
# Boolean:: success
|
|
63
|
+
#
|
|
64
|
+
def run
|
|
65
|
+
command = ['java']
|
|
66
|
+
command << "-Xms#{min_memory}" if min_memory
|
|
67
|
+
command << "-Xmx#{max_memory}" if max_memory
|
|
68
|
+
command << "-Djetty.port=#{port}" if port
|
|
69
|
+
command << "-Dsolr.data.dir=#{solr_data_dir}" if solr_data_dir
|
|
70
|
+
command << "-Dsolr.solr.home=#{solr_home}" if solr_home
|
|
71
|
+
command << "-Djava.util.logging.config.file=#{logging_config_path}" if logging_config_path
|
|
72
|
+
command << '-jar' << File.basename(solr_jar)
|
|
73
|
+
FileUtils.cd(File.dirname(solr_jar)) do
|
|
74
|
+
exec(Escape.shell_command(command))
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
#
|
|
79
|
+
# Stop the sunspot-solr server.
|
|
80
|
+
#
|
|
81
|
+
# ==== Returns
|
|
82
|
+
#
|
|
83
|
+
# Boolean:: success
|
|
84
|
+
#
|
|
85
|
+
def stop
|
|
86
|
+
if File.exist?(pid_path)
|
|
87
|
+
pid = IO.read(pid_path).to_i
|
|
88
|
+
begin
|
|
89
|
+
Process.kill('TERM', pid)
|
|
90
|
+
rescue Errno::ESRCH
|
|
91
|
+
raise NotRunningError, "Process with PID #{pid} is no longer running"
|
|
92
|
+
ensure
|
|
93
|
+
FileUtils.rm(pid_path)
|
|
94
|
+
end
|
|
95
|
+
else
|
|
96
|
+
raise NotRunningError, "No PID file at #{pid_path}"
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def log_level=(level)
|
|
101
|
+
unless LOG_LEVELS.include?(level.to_s.upcase)
|
|
102
|
+
raise(ArgumentError, "#{level} is not a valid log level: Use one of #{LOG_LEVELS.to_a.join(',')}")
|
|
103
|
+
end
|
|
104
|
+
@log_level = level.to_s.upcase
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def log_level
|
|
108
|
+
@log_level || 'WARNING'
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def pid_path
|
|
112
|
+
File.join(pid_dir, pid_file)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def pid_file
|
|
116
|
+
@pid_file || 'sunspot-solr.pid'
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def pid_dir
|
|
120
|
+
File.expand_path(@pid_dir || FileUtils.pwd)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def solr_data_dir
|
|
124
|
+
File.expand_path(@solr_data_dir || Dir.tmpdir)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def solr_home
|
|
128
|
+
File.expand_path(@solr_home || File.join(File.dirname(solr_jar), 'solr'))
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def solr_jar
|
|
132
|
+
@solr_jar || SOLR_START_JAR
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
private
|
|
136
|
+
|
|
137
|
+
def logging_config_path
|
|
138
|
+
return @logging_config_path if defined?(@logging_config_path)
|
|
139
|
+
@logging_config_path =
|
|
140
|
+
if log_file
|
|
141
|
+
logging_config = Tempfile.new('logging.properties')
|
|
142
|
+
logging_config.puts("handlers = java.util.logging.FileHandler")
|
|
143
|
+
logging_config.puts("java.util.logging.FileHandler.level = #{log_level.to_s.upcase}")
|
|
144
|
+
logging_config.puts("java.util.logging.FileHandler.pattern = #{log_file}")
|
|
145
|
+
logging_config.puts("java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter")
|
|
146
|
+
logging_config.flush
|
|
147
|
+
logging_config.close
|
|
148
|
+
logging_config.path
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|