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,76 @@
|
|
|
1
|
+
require 'rubygems'
|
|
2
|
+
require 'fileutils'
|
|
3
|
+
require 'nokogiri'
|
|
4
|
+
|
|
5
|
+
module Sunspot
|
|
6
|
+
class Installer
|
|
7
|
+
class SolrconfigUpdater
|
|
8
|
+
include TaskHelper
|
|
9
|
+
|
|
10
|
+
CONFIG_FILES = %w(solrconfig.xml elevate.xml spellings.txt stopwords.txt synonyms.txt)
|
|
11
|
+
|
|
12
|
+
class <<self
|
|
13
|
+
def execute(solrconfig_path, options = {})
|
|
14
|
+
new(solrconfig_path, options).execute
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def initialize(solrconfig_path, options)
|
|
19
|
+
@force = !!options[:force]
|
|
20
|
+
unless @force || File.exist?(solrconfig_path)
|
|
21
|
+
abort("#{File.expand_path(@solrconfig_path)} doesn't exist." +
|
|
22
|
+
" Are you sure you're pointing to a SOLR_HOME?")
|
|
23
|
+
end
|
|
24
|
+
@solrconfig_path = solrconfig_path
|
|
25
|
+
@verbose = !!options[:verbose]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def execute
|
|
29
|
+
if @force
|
|
30
|
+
config_dir = File.dirname(@solrconfig_path)
|
|
31
|
+
FileUtils.mkdir_p(config_dir)
|
|
32
|
+
CONFIG_FILES.each do |file|
|
|
33
|
+
source_path =
|
|
34
|
+
File.join(File.dirname(__FILE__), '..', '..', '..', 'solr', 'solr', 'conf', file)
|
|
35
|
+
FileUtils.cp(source_path, config_dir)
|
|
36
|
+
say("Copied default #{file} to #{config_dir}")
|
|
37
|
+
end
|
|
38
|
+
else
|
|
39
|
+
@document = File.open(@solrconfig_path) do |f|
|
|
40
|
+
Nokogiri::XML(
|
|
41
|
+
f, nil, nil,
|
|
42
|
+
Nokogiri::XML::ParseOptions::DEFAULT_XML |
|
|
43
|
+
Nokogiri::XML::ParseOptions::NOBLANKS
|
|
44
|
+
)
|
|
45
|
+
end
|
|
46
|
+
@root = @document.root
|
|
47
|
+
maybe_add_more_like_this_handler
|
|
48
|
+
original_path = "#{@solrconfig_path}.orig"
|
|
49
|
+
FileUtils.cp(@solrconfig_path, original_path)
|
|
50
|
+
say("Saved backup of original to #{original_path}")
|
|
51
|
+
File.open(@solrconfig_path, 'w') do |file|
|
|
52
|
+
@document.write_to(
|
|
53
|
+
file,
|
|
54
|
+
:indent => 2
|
|
55
|
+
)
|
|
56
|
+
end
|
|
57
|
+
say("Wrote solrconfig to #{@solrconfig_path}")
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
def maybe_add_more_like_this_handler
|
|
64
|
+
unless @root.xpath('requestHandler[@name="/mlt"]').first
|
|
65
|
+
mlt_node = add_element(
|
|
66
|
+
@root, 'requestHandler',
|
|
67
|
+
:name => '/mlt', :class => 'solr.MoreLikeThisHandler'
|
|
68
|
+
)
|
|
69
|
+
defaults_node = add_element(mlt_node, 'lst', :name => 'defaults')
|
|
70
|
+
add_element(defaults_node, 'str', :name => 'mlt.mintf').content = '1'
|
|
71
|
+
add_element(defaults_node, 'str', :name => 'mlt.mindf').content = '2'
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module Sunspot
|
|
2
|
+
class Installer
|
|
3
|
+
module TaskHelper
|
|
4
|
+
def say(message)
|
|
5
|
+
if @verbose
|
|
6
|
+
STDOUT.puts(message)
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def add_element(node, name, attributes = {})
|
|
11
|
+
new_node = Nokogiri::XML::Node.new(name, @document)
|
|
12
|
+
attributes.each_pair { |name, value| new_node[name.to_s] = value }
|
|
13
|
+
node << new_node
|
|
14
|
+
new_node
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
%w(task_helper library_installer schema_builder solrconfig_updater).each do |file|
|
|
2
|
+
require File.join(File.dirname(__FILE__), 'installer', file)
|
|
3
|
+
end
|
|
4
|
+
|
|
5
|
+
module Sunspot
|
|
6
|
+
class Installer
|
|
7
|
+
class <<self
|
|
8
|
+
def execute(solr_home, options = {})
|
|
9
|
+
new(solr_home, options).execute
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
private :new
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def initialize(solr_home, options)
|
|
16
|
+
@solr_home, @options = solr_home, options
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def execute
|
|
20
|
+
SchemaBuilder.execute(
|
|
21
|
+
File.join(@solr_home, 'conf', 'schema.xml'),
|
|
22
|
+
@options
|
|
23
|
+
)
|
|
24
|
+
SolrconfigUpdater.execute(
|
|
25
|
+
File.join(@solr_home, 'conf', 'solrconfig.xml'),
|
|
26
|
+
@options
|
|
27
|
+
)
|
|
28
|
+
LibraryInstaller.execute(File.join(@solr_home, 'lib'), @options)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
module Sunspot
|
|
2
|
+
module Query
|
|
3
|
+
class AbstractFieldFacet
|
|
4
|
+
include RSolr::Char
|
|
5
|
+
|
|
6
|
+
def initialize(field, options)
|
|
7
|
+
@field, @options = field, options
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def to_params
|
|
11
|
+
params = {
|
|
12
|
+
:facet => 'true',
|
|
13
|
+
}
|
|
14
|
+
case @options[:sort]
|
|
15
|
+
when :count
|
|
16
|
+
params[qualified_param('sort')] = 'true'
|
|
17
|
+
when :index
|
|
18
|
+
params[qualified_param('sort')] = 'false'
|
|
19
|
+
when nil
|
|
20
|
+
else
|
|
21
|
+
raise(
|
|
22
|
+
ArgumentError,
|
|
23
|
+
"#{@options[:sort].inspect} is not an allowed value for :sort. Allowed options are :count and :index"
|
|
24
|
+
)
|
|
25
|
+
end
|
|
26
|
+
if @options[:limit]
|
|
27
|
+
params[qualified_param('limit')] = @options[:limit].to_i
|
|
28
|
+
end
|
|
29
|
+
if @options[:prefix]
|
|
30
|
+
params[qualified_param('prefix')] = escape(@options[:prefix].to_s)
|
|
31
|
+
end
|
|
32
|
+
params[qualified_param('mincount')] =
|
|
33
|
+
case
|
|
34
|
+
when @options[:minimum_count] then @options[:minimum_count].to_i
|
|
35
|
+
when @options[:zeros] then 0
|
|
36
|
+
else 1
|
|
37
|
+
end
|
|
38
|
+
params
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def qualified_param(param)
|
|
44
|
+
:"f.#{key}.facet.#{param}"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def key
|
|
48
|
+
@key ||= @options[:name] || @field.indexed_name
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module Sunspot
|
|
2
|
+
module Query
|
|
3
|
+
#
|
|
4
|
+
# Representation of a BoostQuery, which allows the searcher to specify a
|
|
5
|
+
# scope for which matching documents should have an extra boost. This is
|
|
6
|
+
# essentially a conjunction, with an extra instance variable containing
|
|
7
|
+
# the boost that should be applied.
|
|
8
|
+
#
|
|
9
|
+
class BoostQuery < Connective::Conjunction #:nodoc:
|
|
10
|
+
def initialize(boost)
|
|
11
|
+
super(false)
|
|
12
|
+
@boost = boost
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def to_boolean_phrase
|
|
16
|
+
if @boost.is_a?(FunctionQuery)
|
|
17
|
+
"#{@boost}"
|
|
18
|
+
else
|
|
19
|
+
"#{super}^#{@boost}"
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
module Sunspot
|
|
2
|
+
module Query #:nodoc:
|
|
3
|
+
class CommonQuery
|
|
4
|
+
def initialize(types)
|
|
5
|
+
@scope = Scope.new
|
|
6
|
+
@sort = SortComposite.new
|
|
7
|
+
@components = [@scope, @sort]
|
|
8
|
+
if types.length == 1
|
|
9
|
+
@scope.add_positive_restriction(TypeField.instance, Restriction::EqualTo, types.first)
|
|
10
|
+
else
|
|
11
|
+
@scope.add_positive_restriction(TypeField.instance, Restriction::AnyOf, types)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def solr_parameter_adjustment=(block)
|
|
16
|
+
@parameter_adjustment = block
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def add_sort(sort)
|
|
20
|
+
@sort << sort
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def add_field_facet(facet)
|
|
24
|
+
@components << facet
|
|
25
|
+
facet
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def add_query_facet(facet)
|
|
29
|
+
@components << facet
|
|
30
|
+
facet
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def add_function(function)
|
|
34
|
+
@components << function
|
|
35
|
+
function
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def paginate(page, per_page)
|
|
39
|
+
if @pagination
|
|
40
|
+
@pagination.page = page
|
|
41
|
+
@pagination.per_page = per_page
|
|
42
|
+
else
|
|
43
|
+
@components << @pagination = Pagination.new(page, per_page)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def to_params
|
|
48
|
+
params = {}
|
|
49
|
+
@components.each do |component|
|
|
50
|
+
Sunspot::Util.deep_merge!(params, component.to_params)
|
|
51
|
+
end
|
|
52
|
+
@parameter_adjustment.call(params) if @parameter_adjustment
|
|
53
|
+
params[:q] ||= '*:*'
|
|
54
|
+
params
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def [](key)
|
|
58
|
+
to_params[key.to_sym]
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def page
|
|
62
|
+
@pagination.page if @pagination
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def per_page
|
|
66
|
+
@pagination.per_page if @pagination
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
private
|
|
71
|
+
|
|
72
|
+
#
|
|
73
|
+
# If we have a single fulltext query, merge is normally. If there are
|
|
74
|
+
# multiple nested queries, serialize them as `_query_` subqueries.
|
|
75
|
+
#
|
|
76
|
+
def merge_fulltext(params)
|
|
77
|
+
return nil if @fulltexts.empty?
|
|
78
|
+
return Sunspot::Util.deep_merge!(params, @fulltexts.first.to_params) if @fulltexts.length == 1
|
|
79
|
+
subqueries = @fulltexts.map {|fulltext| fulltext.to_subquery }.join(' ')
|
|
80
|
+
Sunspot::Util.deep_merge!(params, {:q => subqueries})
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
module Sunspot
|
|
2
|
+
module Query
|
|
3
|
+
class CompositeFulltext
|
|
4
|
+
def initialize
|
|
5
|
+
@components = []
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def add(keywords)
|
|
9
|
+
@components << dismax = Dismax.new(keywords)
|
|
10
|
+
dismax
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def add_location(field, lat, lng, options)
|
|
14
|
+
@components << location = Geo.new(field, lat, lng, options)
|
|
15
|
+
location
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def to_params
|
|
19
|
+
case @components.length
|
|
20
|
+
when 0
|
|
21
|
+
{}
|
|
22
|
+
when 1
|
|
23
|
+
@components.first.to_params.merge(:fl => '* score')
|
|
24
|
+
else
|
|
25
|
+
to_subqueries.merge(:fl => '* score')
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def to_subqueries
|
|
32
|
+
{ :q => @components.map { |dismax| dismax.to_subquery }.join(' ') }
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
module Sunspot
|
|
2
|
+
module Query
|
|
3
|
+
module Connective #:nodoc:all
|
|
4
|
+
#
|
|
5
|
+
# Base class for connectives (conjunctions and disjunctions).
|
|
6
|
+
#
|
|
7
|
+
class Abstract
|
|
8
|
+
include Filter
|
|
9
|
+
|
|
10
|
+
def initialize(negated = false) #:nodoc:
|
|
11
|
+
@negated = negated
|
|
12
|
+
@components = []
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
#
|
|
16
|
+
# Add a restriction to the connective.
|
|
17
|
+
#
|
|
18
|
+
def add_restriction(negated, field, restriction_type, *value)
|
|
19
|
+
add_component(restriction_type.new(negated, field, *value))
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
#
|
|
23
|
+
# Add a shorthand restriction; the restriction type is determined by
|
|
24
|
+
# the value.
|
|
25
|
+
#
|
|
26
|
+
def add_shorthand_restriction(negated, field, value)
|
|
27
|
+
restriction_type =
|
|
28
|
+
case value
|
|
29
|
+
when Array then Restriction::AnyOf
|
|
30
|
+
when Range then Restriction::Between
|
|
31
|
+
else Restriction::EqualTo
|
|
32
|
+
end
|
|
33
|
+
add_restriction(negated, field, restriction_type, value)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
#
|
|
37
|
+
# Add a positive restriction. The restriction will match all
|
|
38
|
+
# documents who match the terms fo the restriction.
|
|
39
|
+
#
|
|
40
|
+
def add_positive_restriction(field, restriction_type, value)
|
|
41
|
+
add_restriction(false, field, restriction_type, value)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
#
|
|
45
|
+
# Add a positive shorthand restriction (see add_shorthand_restriction)
|
|
46
|
+
#
|
|
47
|
+
def add_positive_shorthand_restriction(field, value)
|
|
48
|
+
add_shorthand_restriction(false, field, value)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
#
|
|
52
|
+
# Add a negated restriction. The added restriction will match all
|
|
53
|
+
# documents who do not match the terms of the restriction.
|
|
54
|
+
#
|
|
55
|
+
def add_negated_restriction(field, restriction_type, value)
|
|
56
|
+
add_restriction(true, field, restriction_type, value)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
#
|
|
60
|
+
# Add a negated shorthand restriction (see add_shorthand_restriction)
|
|
61
|
+
#
|
|
62
|
+
def add_negated_shorthand_restriction(field, value)
|
|
63
|
+
add_shorthand_restriction(true, field, value)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
#
|
|
67
|
+
# Add a new conjunction and return it.
|
|
68
|
+
#
|
|
69
|
+
def add_conjunction
|
|
70
|
+
add_component(Conjunction.new)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
#
|
|
74
|
+
# Add a new disjunction and return it.
|
|
75
|
+
#
|
|
76
|
+
def add_disjunction
|
|
77
|
+
add_component(Disjunction.new)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
#
|
|
81
|
+
# Add an arbitrary component to the conjunction, and return it.
|
|
82
|
+
# The component must respond to #to_boolean_phrase
|
|
83
|
+
#
|
|
84
|
+
def add_component(component)
|
|
85
|
+
@components << component
|
|
86
|
+
component
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
#
|
|
90
|
+
# Express the connective as a Lucene boolean phrase.
|
|
91
|
+
#
|
|
92
|
+
def to_boolean_phrase #:nodoc:
|
|
93
|
+
unless @components.empty?
|
|
94
|
+
phrase =
|
|
95
|
+
if @components.length == 1
|
|
96
|
+
@components.first.to_boolean_phrase
|
|
97
|
+
else
|
|
98
|
+
component_phrases = @components.map do |component|
|
|
99
|
+
component.to_boolean_phrase
|
|
100
|
+
end
|
|
101
|
+
"(#{component_phrases.join(" #{connector} ")})"
|
|
102
|
+
end
|
|
103
|
+
if negated?
|
|
104
|
+
"-#{phrase}"
|
|
105
|
+
else
|
|
106
|
+
phrase
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
#
|
|
112
|
+
# Connectives can be negated during the process of denormalization that
|
|
113
|
+
# is performed when a disjunction contains a negated component. This
|
|
114
|
+
# method conforms to the duck type for all boolean query components.
|
|
115
|
+
#
|
|
116
|
+
def negated?
|
|
117
|
+
@negated
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
#
|
|
121
|
+
# Returns a new connective that's a negated version of this one.
|
|
122
|
+
#
|
|
123
|
+
def negate
|
|
124
|
+
negated = self.class.new(!negated?)
|
|
125
|
+
@components.each do |component|
|
|
126
|
+
negated.add_component(component)
|
|
127
|
+
end
|
|
128
|
+
negated
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
#
|
|
133
|
+
# Disjunctions combine their components with an OR operator.
|
|
134
|
+
#
|
|
135
|
+
class Disjunction < Abstract
|
|
136
|
+
class <<self
|
|
137
|
+
def inverse
|
|
138
|
+
Conjunction
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
#
|
|
143
|
+
# Express this disjunction as a Lucene boolean phrase
|
|
144
|
+
#
|
|
145
|
+
def to_boolean_phrase
|
|
146
|
+
if @components.any? { |component| component.negated? }
|
|
147
|
+
denormalize.to_boolean_phrase
|
|
148
|
+
else
|
|
149
|
+
super
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
#
|
|
154
|
+
# No-op - this is already a disjunction
|
|
155
|
+
#
|
|
156
|
+
def add_disjunction
|
|
157
|
+
self
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
private
|
|
161
|
+
|
|
162
|
+
def connector
|
|
163
|
+
'OR'
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
#
|
|
167
|
+
# If a disjunction contains negated components, it must be
|
|
168
|
+
# "denormalized", because the Lucene parser interprets any negated
|
|
169
|
+
# boolean phrase using AND semantics (this isn't a bug, it's just a
|
|
170
|
+
# subtlety of how Lucene parses queries). So, per DeMorgan's law we
|
|
171
|
+
# create a negated conjunction and add to it all of our components,
|
|
172
|
+
# negated themselves, which creates a query whose Lucene semantics are
|
|
173
|
+
# in line with our intentions.
|
|
174
|
+
#
|
|
175
|
+
def denormalize
|
|
176
|
+
denormalized = self.class.inverse.new(!negated?)
|
|
177
|
+
@components.each do |component|
|
|
178
|
+
denormalized.add_component(component.negate)
|
|
179
|
+
end
|
|
180
|
+
denormalized
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
#
|
|
185
|
+
# Conjunctions combine their components with an AND operator.
|
|
186
|
+
#
|
|
187
|
+
class Conjunction < Abstract
|
|
188
|
+
class <<self
|
|
189
|
+
def inverse
|
|
190
|
+
Disjunction
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def add_conjunction
|
|
195
|
+
self
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
private
|
|
199
|
+
|
|
200
|
+
def connector
|
|
201
|
+
'AND'
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module Sunspot
|
|
2
|
+
module Query
|
|
3
|
+
class DateFieldFacet < AbstractFieldFacet
|
|
4
|
+
def to_params
|
|
5
|
+
params = super
|
|
6
|
+
params[:"facet.date"] = [@field.indexed_name]
|
|
7
|
+
params[qualified_param('date.start')] = @field.to_indexed(@options[:time_range].first)
|
|
8
|
+
params[qualified_param('date.end')] = @field.to_indexed(@options[:time_range].last)
|
|
9
|
+
params[qualified_param('date.gap')] = "+#{@options[:time_interval] || 86400}SECONDS"
|
|
10
|
+
params
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
module Sunspot
|
|
2
|
+
module Query
|
|
3
|
+
|
|
4
|
+
#
|
|
5
|
+
# Solr full-text queries use Solr's DisMaxRequestHandler, a search handler
|
|
6
|
+
# designed to process user-entered phrases, and search for individual
|
|
7
|
+
# words across a union of several fields.
|
|
8
|
+
#
|
|
9
|
+
class Dismax
|
|
10
|
+
attr_writer :minimum_match, :phrase_slop, :query_phrase_slop, :tie
|
|
11
|
+
|
|
12
|
+
def initialize(keywords)
|
|
13
|
+
@keywords = keywords
|
|
14
|
+
@fulltext_fields = {}
|
|
15
|
+
@boost_queries = []
|
|
16
|
+
@boost_functions = []
|
|
17
|
+
@highlights = []
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
#
|
|
21
|
+
# The query as Solr parameters
|
|
22
|
+
#
|
|
23
|
+
def to_params
|
|
24
|
+
params = { :q => @keywords }
|
|
25
|
+
params[:fl] = '* score'
|
|
26
|
+
params[:qf] = @fulltext_fields.values.map { |field| field.to_boosted_field }.join(' ')
|
|
27
|
+
params[:defType] = 'dismax'
|
|
28
|
+
if @phrase_fields
|
|
29
|
+
params[:pf] = @phrase_fields.map { |field| field.to_boosted_field }.join(' ')
|
|
30
|
+
end
|
|
31
|
+
unless @boost_queries.empty?
|
|
32
|
+
params[:bq] = @boost_queries.map do |boost_query|
|
|
33
|
+
boost_query.to_boolean_phrase
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
unless @boost_functions.empty?
|
|
37
|
+
params[:bf] = @boost_functions.map do |boost_function|
|
|
38
|
+
boost_function.to_s
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
if @minimum_match
|
|
42
|
+
params[:mm] = @minimum_match
|
|
43
|
+
end
|
|
44
|
+
if @phrase_slop
|
|
45
|
+
params[:ps] = @phrase_slop
|
|
46
|
+
end
|
|
47
|
+
if @query_phrase_slop
|
|
48
|
+
params[:qs] = @query_phrase_slop
|
|
49
|
+
end
|
|
50
|
+
if @tie
|
|
51
|
+
params[:tie] = @tie
|
|
52
|
+
end
|
|
53
|
+
@highlights.each do |highlight|
|
|
54
|
+
Sunspot::Util.deep_merge!(params, highlight.to_params)
|
|
55
|
+
end
|
|
56
|
+
params
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
#
|
|
60
|
+
# Serialize the query as a Solr nested subquery.
|
|
61
|
+
#
|
|
62
|
+
def to_subquery
|
|
63
|
+
params = self.to_params
|
|
64
|
+
params.delete :defType
|
|
65
|
+
params.delete :fl
|
|
66
|
+
keywords = params.delete(:q)
|
|
67
|
+
options = params.map { |key, value| "#{key}='#{escape_quotes(value)}'"}.join(' ')
|
|
68
|
+
"_query_:\"{!dismax #{options}}#{escape_quotes(keywords)}\""
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
#
|
|
72
|
+
# Assign a new boost query and return it.
|
|
73
|
+
#
|
|
74
|
+
def create_boost_query(factor)
|
|
75
|
+
@boost_queries << boost_query = BoostQuery.new(factor)
|
|
76
|
+
boost_query
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
#
|
|
80
|
+
# Add a boost function
|
|
81
|
+
#
|
|
82
|
+
def add_boost_function(function_query)
|
|
83
|
+
@boost_functions << function_query
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
#
|
|
87
|
+
# Add a fulltext field to be searched, with optional boost.
|
|
88
|
+
#
|
|
89
|
+
def add_fulltext_field(field, boost = nil)
|
|
90
|
+
@fulltext_fields[field.indexed_name] = TextFieldBoost.new(field, boost)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
#
|
|
94
|
+
# Add a phrase field for extra boost.
|
|
95
|
+
#
|
|
96
|
+
def add_phrase_field(field, boost = nil)
|
|
97
|
+
@phrase_fields ||= []
|
|
98
|
+
@phrase_fields << TextFieldBoost.new(field, boost)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
#
|
|
102
|
+
# Set highlighting options for the query. If fields is empty, the
|
|
103
|
+
# Highlighting object won't pass field names at all, which means
|
|
104
|
+
# the dismax's :qf parameter will be used by Solr.
|
|
105
|
+
#
|
|
106
|
+
def add_highlight(fields=[], options={})
|
|
107
|
+
@highlights << Highlighting.new(fields, options)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
#
|
|
111
|
+
# Determine if a given field is being searched. Used by DSL to avoid
|
|
112
|
+
# overwriting boost parameters when injecting defaults.
|
|
113
|
+
#
|
|
114
|
+
def has_fulltext_field?(field)
|
|
115
|
+
@fulltext_fields.has_key?(field.indexed_name)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
private
|
|
120
|
+
|
|
121
|
+
def escape_quotes(value)
|
|
122
|
+
return value unless value.is_a? String
|
|
123
|
+
value.gsub(/(['"])/, '\\\\\1')
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|