thinking-sphinx 3.0.6 → 3.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +11 -6
- data/Appraisals +5 -5
- data/Gemfile +3 -3
- data/HISTORY +32 -0
- data/README.textile +16 -5
- data/gemfiles/rails_3_2.gemfile +2 -1
- data/gemfiles/rails_4_0.gemfile +3 -2
- data/gemfiles/{rails_3_1.gemfile → rails_4_1.gemfile} +3 -2
- data/lib/thinking_sphinx.rb +5 -0
- data/lib/thinking_sphinx/active_record.rb +2 -1
- data/lib/thinking_sphinx/active_record/association_proxy/attribute_finder.rb +1 -1
- data/lib/thinking_sphinx/active_record/callbacks/delete_callbacks.rb +6 -7
- data/lib/thinking_sphinx/active_record/callbacks/delta_callbacks.rb +2 -2
- data/lib/thinking_sphinx/active_record/callbacks/update_callbacks.rb +1 -1
- data/lib/thinking_sphinx/active_record/column_sql_presenter.rb +34 -0
- data/lib/thinking_sphinx/active_record/database_adapters/mysql_adapter.rb +4 -0
- data/lib/thinking_sphinx/active_record/database_adapters/postgresql_adapter.rb +4 -0
- data/lib/thinking_sphinx/active_record/index.rb +2 -1
- data/lib/thinking_sphinx/active_record/interpreter.rb +7 -0
- data/lib/thinking_sphinx/active_record/property.rb +1 -1
- data/lib/thinking_sphinx/active_record/property_sql_presenter.rb +10 -16
- data/lib/thinking_sphinx/active_record/sql_builder.rb +7 -1
- data/lib/thinking_sphinx/active_record/sql_builder/query.rb +0 -7
- data/lib/thinking_sphinx/active_record/sql_source.rb +20 -20
- data/lib/thinking_sphinx/active_record/sql_source/template.rb +1 -1
- data/lib/thinking_sphinx/capistrano.rb +6 -65
- data/lib/thinking_sphinx/capistrano/v2.rb +58 -0
- data/lib/thinking_sphinx/capistrano/v3.rb +101 -0
- data/lib/thinking_sphinx/configuration.rb +8 -3
- data/lib/thinking_sphinx/configuration/distributed_indices.rb +29 -0
- data/lib/thinking_sphinx/connection.rb +90 -34
- data/lib/thinking_sphinx/controller.rb +20 -0
- data/lib/thinking_sphinx/core/index.rb +4 -0
- data/lib/thinking_sphinx/deletion.rb +15 -11
- data/lib/thinking_sphinx/deltas.rb +9 -0
- data/lib/thinking_sphinx/deltas/default_delta.rb +2 -0
- data/lib/thinking_sphinx/distributed.rb +5 -0
- data/lib/thinking_sphinx/distributed/index.rb +24 -0
- data/lib/thinking_sphinx/excerpter.rb +7 -3
- data/lib/thinking_sphinx/facet_search.rb +1 -1
- data/lib/thinking_sphinx/index.rb +2 -6
- data/lib/thinking_sphinx/index_set.rb +10 -8
- data/lib/thinking_sphinx/middlewares.rb +0 -2
- data/lib/thinking_sphinx/middlewares/active_record_translator.rb +1 -0
- data/lib/thinking_sphinx/middlewares/geographer.rb +1 -1
- data/lib/thinking_sphinx/middlewares/sphinxql.rb +8 -6
- data/lib/thinking_sphinx/middlewares/utf8.rb +6 -1
- data/lib/thinking_sphinx/query.rb +9 -0
- data/lib/thinking_sphinx/railtie.rb +0 -13
- data/lib/thinking_sphinx/search/query.rb +3 -21
- data/lib/thinking_sphinx/sphinxql.rb +1 -1
- data/lib/thinking_sphinx/wildcard.rb +34 -0
- data/spec/acceptance/geosearching_spec.rb +13 -0
- data/spec/acceptance/indexing_spec.rb +27 -0
- data/spec/acceptance/remove_deleted_records_spec.rb +8 -0
- data/spec/acceptance/searching_with_sti_spec.rb +7 -0
- data/spec/acceptance/searching_within_a_model_spec.rb +8 -0
- data/spec/acceptance/sorting_search_results_spec.rb +1 -1
- data/spec/acceptance/spec_helper.rb +13 -0
- data/spec/acceptance/specifying_sql_spec.rb +2 -2
- data/spec/acceptance/support/sphinx_controller.rb +5 -5
- data/spec/acceptance/support/sphinx_helpers.rb +3 -0
- data/spec/acceptance/suspended_deltas_spec.rb +34 -0
- data/spec/internal/config/database.yml +1 -0
- data/spec/thinking_sphinx/active_record/callbacks/delete_callbacks_spec.rb +13 -6
- data/spec/thinking_sphinx/active_record/callbacks/update_callbacks_spec.rb +2 -2
- data/spec/thinking_sphinx/active_record/database_adapters/mysql_adapter_spec.rb +7 -0
- data/spec/thinking_sphinx/active_record/database_adapters/postgresql_adapter_spec.rb +6 -0
- data/spec/thinking_sphinx/active_record/interpreter_spec.rb +27 -0
- data/spec/thinking_sphinx/active_record/property_sql_presenter_spec.rb +18 -7
- data/spec/thinking_sphinx/active_record/sql_builder_spec.rb +17 -7
- data/spec/thinking_sphinx/active_record/sql_source_spec.rb +84 -82
- data/spec/thinking_sphinx/configuration_spec.rb +5 -4
- data/spec/thinking_sphinx/connection_spec.rb +1 -1
- data/spec/thinking_sphinx/deletion_spec.rb +10 -28
- data/spec/thinking_sphinx/excerpter_spec.rb +3 -3
- data/spec/thinking_sphinx/facet_search_spec.rb +13 -4
- data/spec/thinking_sphinx/index_set_spec.rb +9 -4
- data/spec/thinking_sphinx/middlewares/active_record_translator_spec.rb +8 -0
- data/spec/thinking_sphinx/middlewares/geographer_spec.rb +7 -7
- data/spec/thinking_sphinx/middlewares/sphinxql_spec.rb +17 -1
- data/spec/thinking_sphinx/search/query_spec.rb +10 -53
- data/spec/thinking_sphinx/wildcard_spec.rb +41 -0
- data/thinking-sphinx.gemspec +6 -5
- metadata +38 -14
- data/lib/thinking_sphinx/active_record/associations.rb +0 -98
- data/spec/thinking_sphinx/active_record/associations_spec.rb +0 -230
@@ -0,0 +1,20 @@
|
|
1
|
+
class ThinkingSphinx::Controller < Riddle::Controller
|
2
|
+
def index(*indices)
|
3
|
+
options = indices.extract_options!
|
4
|
+
indices << '--all' if indices.empty?
|
5
|
+
|
6
|
+
indices = indices.reject { |index| File.exists? guard_file(index) }
|
7
|
+
return if indices.empty?
|
8
|
+
|
9
|
+
indices.each { |index| FileUtils.touch guard_file(index) }
|
10
|
+
super(*(indices + [options]))
|
11
|
+
indices.each { |index| FileUtils.rm guard_file(index) }
|
12
|
+
end
|
13
|
+
|
14
|
+
def guard_file(index)
|
15
|
+
File.join(
|
16
|
+
ThinkingSphinx::Configuration.instance.indices_location,
|
17
|
+
"ts-#{index}.tmp"
|
18
|
+
)
|
19
|
+
end
|
20
|
+
end
|
@@ -1,25 +1,27 @@
|
|
1
1
|
class ThinkingSphinx::Deletion
|
2
2
|
delegate :name, :to => :index
|
3
3
|
|
4
|
-
def self.perform(index,
|
4
|
+
def self.perform(index, ids)
|
5
|
+
return if index.distributed?
|
6
|
+
|
5
7
|
{
|
6
8
|
'plain' => PlainDeletion,
|
7
9
|
'rt' => RealtimeDeletion
|
8
|
-
}[index.type].new(index,
|
10
|
+
}[index.type].new(index, ids).perform
|
9
11
|
rescue ThinkingSphinx::ConnectionError => error
|
10
12
|
# This isn't vital, so don't raise the error.
|
11
13
|
end
|
12
14
|
|
13
|
-
def initialize(index,
|
14
|
-
@index, @
|
15
|
+
def initialize(index, ids)
|
16
|
+
@index, @ids = index, Array(ids)
|
15
17
|
end
|
16
18
|
|
17
19
|
private
|
18
20
|
|
19
|
-
attr_reader :index, :
|
21
|
+
attr_reader :index, :ids
|
20
22
|
|
21
|
-
def
|
22
|
-
index.document_id_for_key
|
23
|
+
def document_ids_for_keys
|
24
|
+
ids.collect { |id| index.document_id_for_key id }
|
23
25
|
end
|
24
26
|
|
25
27
|
def execute(statement)
|
@@ -30,15 +32,17 @@ class ThinkingSphinx::Deletion
|
|
30
32
|
|
31
33
|
class RealtimeDeletion < ThinkingSphinx::Deletion
|
32
34
|
def perform
|
33
|
-
execute Riddle::Query::Delete.new(name,
|
35
|
+
execute Riddle::Query::Delete.new(name, document_ids_for_keys).to_sql
|
34
36
|
end
|
35
37
|
end
|
36
38
|
|
37
39
|
class PlainDeletion < ThinkingSphinx::Deletion
|
38
40
|
def perform
|
39
|
-
execute
|
40
|
-
|
41
|
-
|
41
|
+
execute <<-SQL
|
42
|
+
UPDATE #{name}
|
43
|
+
SET sphinx_deleted = 1
|
44
|
+
WHERE id IN (#{document_ids_for_keys.join(', ')})
|
45
|
+
SQL
|
42
46
|
end
|
43
47
|
end
|
44
48
|
end
|
@@ -30,6 +30,15 @@ module ThinkingSphinx::Deltas
|
|
30
30
|
end
|
31
31
|
end
|
32
32
|
|
33
|
+
def self.suspend_and_update(reference, &block)
|
34
|
+
suspend reference, &block
|
35
|
+
|
36
|
+
ids = reference.to_s.camelize.constantize.where(delta: true).pluck(:id)
|
37
|
+
config.indices_for_references(reference).each do |index|
|
38
|
+
ThinkingSphinx::Deletion.perform index, ids unless index.delta?
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
33
42
|
def self.suspend!
|
34
43
|
@suspended = true
|
35
44
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
class ThinkingSphinx::Distributed::Index <
|
2
|
+
Riddle::Configuration::DistributedIndex
|
3
|
+
|
4
|
+
attr_reader :reference, :options
|
5
|
+
|
6
|
+
def initialize(reference)
|
7
|
+
@reference = reference
|
8
|
+
@options = {}
|
9
|
+
|
10
|
+
super reference.to_s.gsub('/', '_')
|
11
|
+
end
|
12
|
+
|
13
|
+
def delta?
|
14
|
+
false
|
15
|
+
end
|
16
|
+
|
17
|
+
def distributed?
|
18
|
+
true
|
19
|
+
end
|
20
|
+
|
21
|
+
def model
|
22
|
+
@model ||= reference.to_s.camelize.constantize
|
23
|
+
end
|
24
|
+
end
|
@@ -15,11 +15,10 @@ class ThinkingSphinx::Excerpter
|
|
15
15
|
|
16
16
|
def excerpt!(text)
|
17
17
|
result = ThinkingSphinx::Connection.take do |connection|
|
18
|
-
connection.
|
18
|
+
connection.execute(statement_for(text)).first['snippet']
|
19
19
|
end
|
20
20
|
|
21
|
-
|
22
|
-
ThinkingSphinx::UTF8.encode(result)
|
21
|
+
encoded? ? result : ThinkingSphinx::UTF8.encode(result)
|
23
22
|
end
|
24
23
|
|
25
24
|
private
|
@@ -27,4 +26,9 @@ class ThinkingSphinx::Excerpter
|
|
27
26
|
def statement_for(text)
|
28
27
|
Riddle::Query.snippets(text, index, words, options)
|
29
28
|
end
|
29
|
+
|
30
|
+
def encoded?
|
31
|
+
ThinkingSphinx::Configuration.instance.settings['utf8'].nil? ||
|
32
|
+
ThinkingSphinx::Configuration.instance.settings['utf8']
|
33
|
+
end
|
30
34
|
end
|
@@ -105,7 +105,7 @@ class ThinkingSphinx::FacetSearch
|
|
105
105
|
", #{ThinkingSphinx::SphinxQL.group_by}, #{ThinkingSphinx::SphinxQL.count}",
|
106
106
|
:group_by => facet.name,
|
107
107
|
:indices => index_names_for(facet),
|
108
|
-
:max_matches =>
|
108
|
+
:max_matches => max_matches,
|
109
109
|
:limit => limit
|
110
110
|
)
|
111
111
|
end
|
@@ -11,16 +11,12 @@ class ThinkingSphinx::Index
|
|
11
11
|
defaults = ThinkingSphinx::Configuration.instance.
|
12
12
|
settings['index_options'] || {}
|
13
13
|
defaults.symbolize_keys!
|
14
|
-
|
14
|
+
|
15
15
|
@reference, @options, @block = reference, defaults.merge(options), block
|
16
16
|
end
|
17
17
|
|
18
18
|
def indices
|
19
|
-
|
20
|
-
delta_indices
|
21
|
-
else
|
22
|
-
[single_index]
|
23
|
-
end
|
19
|
+
options[:delta] ? delta_indices : [single_index]
|
24
20
|
end
|
25
21
|
|
26
22
|
private
|
@@ -19,8 +19,10 @@ class ThinkingSphinx::IndexSet
|
|
19
19
|
|
20
20
|
private
|
21
21
|
|
22
|
+
attr_reader :classes, :configuration, :index_names
|
23
|
+
|
22
24
|
def classes_and_ancestors
|
23
|
-
@classes_and_ancestors ||=
|
25
|
+
@classes_and_ancestors ||= classes.collect { |model|
|
24
26
|
model.ancestors.take_while { |klass|
|
25
27
|
klass != ActiveRecord::Base
|
26
28
|
}.select { |klass|
|
@@ -30,15 +32,15 @@ class ThinkingSphinx::IndexSet
|
|
30
32
|
end
|
31
33
|
|
32
34
|
def indices
|
33
|
-
|
34
|
-
|
35
|
-
return @configuration.indices.select { |index|
|
36
|
-
@index_names.include?(index.name)
|
37
|
-
} if @index_names && @index_names.any?
|
35
|
+
configuration.preload_indices
|
38
36
|
|
39
|
-
return
|
37
|
+
return configuration.indices.select { |index|
|
38
|
+
index_names.include?(index.name)
|
39
|
+
} if index_names && index_names.any?
|
40
40
|
|
41
|
-
|
41
|
+
everything = classes.empty? ? configuration.indices :
|
42
|
+
configuration.indices_for_references(*references)
|
43
|
+
everything.reject &:distributed?
|
42
44
|
end
|
43
45
|
|
44
46
|
def references
|
@@ -15,7 +15,6 @@ module ThinkingSphinx::Middlewares
|
|
15
15
|
DEFAULT = ::Middleware::Builder.new do
|
16
16
|
use StaleIdFilter
|
17
17
|
ThinkingSphinx::Middlewares.use self, BASE_MIDDLEWARES
|
18
|
-
use UTF8
|
19
18
|
use ActiveRecordTranslator
|
20
19
|
use StaleIdChecker
|
21
20
|
use Glazier
|
@@ -23,7 +22,6 @@ module ThinkingSphinx::Middlewares
|
|
23
22
|
|
24
23
|
RAW_ONLY = ::Middleware::Builder.new do
|
25
24
|
ThinkingSphinx::Middlewares.use self, BASE_MIDDLEWARES
|
26
|
-
use UTF8
|
27
25
|
end
|
28
26
|
|
29
27
|
IDS_ONLY = ::Middleware::Builder.new do
|
@@ -69,6 +69,7 @@ class ThinkingSphinx::Middlewares::ActiveRecordTranslator <
|
|
69
69
|
relation = relation.joins sql_options[:joins] if sql_options[:joins]
|
70
70
|
relation = relation.order sql_options[:order] if sql_options[:order]
|
71
71
|
relation = relation.select sql_options[:select] if sql_options[:select]
|
72
|
+
relation = relation.group sql_options[:group] if sql_options[:group]
|
72
73
|
relation
|
73
74
|
end
|
74
75
|
|
@@ -1,10 +1,10 @@
|
|
1
1
|
class ThinkingSphinx::Middlewares::SphinxQL <
|
2
2
|
ThinkingSphinx::Middlewares::Middleware
|
3
3
|
|
4
|
-
SELECT_OPTIONS = [:
|
5
|
-
:
|
6
|
-
:
|
7
|
-
:sort_method]
|
4
|
+
SELECT_OPTIONS = [:agent_query_timeout, :boolean_simplify, :comment, :cutoff,
|
5
|
+
:field_weights, :global_idf, :idf, :index_weights, :max_matches,
|
6
|
+
:max_query_time, :max_predicted_time, :ranker, :retry_count, :retry_delay,
|
7
|
+
:reverse_scan, :sort_method]
|
8
8
|
|
9
9
|
def call(contexts)
|
10
10
|
contexts.each do |context|
|
@@ -197,7 +197,7 @@ SQL
|
|
197
197
|
end
|
198
198
|
|
199
199
|
def scope_by_values
|
200
|
-
query.values values
|
200
|
+
query.values(values.present? ? values : '*')
|
201
201
|
end
|
202
202
|
|
203
203
|
def scope_by_extended_query
|
@@ -225,8 +225,10 @@ SQL
|
|
225
225
|
end
|
226
226
|
|
227
227
|
def scope_by_group
|
228
|
-
query.group_by group_attribute
|
228
|
+
query.group_by group_attribute if group_attribute.present?
|
229
|
+
query.group_best options[:group_best] if options[:group_best]
|
229
230
|
query.order_within_group_by group_order_clause if group_order_clause.present?
|
231
|
+
query.having options[:having] if options[:having]
|
230
232
|
end
|
231
233
|
|
232
234
|
def scope_by_pagination
|
@@ -5,13 +5,18 @@ class ThinkingSphinx::Middlewares::UTF8 <
|
|
5
5
|
contexts.each do |context|
|
6
6
|
context[:results].each { |row| update_row row }
|
7
7
|
update_row context[:meta]
|
8
|
-
end unless
|
8
|
+
end unless encoded?
|
9
9
|
|
10
10
|
app.call contexts
|
11
11
|
end
|
12
12
|
|
13
13
|
private
|
14
14
|
|
15
|
+
def encoded?
|
16
|
+
ThinkingSphinx::Configuration.instance.settings['utf8'].nil? ||
|
17
|
+
ThinkingSphinx::Configuration.instance.settings['utf8']
|
18
|
+
end
|
19
|
+
|
15
20
|
def update_row(row)
|
16
21
|
row.each do |key, value|
|
17
22
|
next unless value.is_a?(String)
|
@@ -7,16 +7,3 @@ class ThinkingSphinx::Railtie < Rails::Railtie
|
|
7
7
|
load File.expand_path('../tasks.rb', __FILE__)
|
8
8
|
end
|
9
9
|
end
|
10
|
-
|
11
|
-
# Add 'app/indices' path to Rails Engines
|
12
|
-
module ThinkingSphinx::EnginePaths
|
13
|
-
extend ActiveSupport::Concern
|
14
|
-
|
15
|
-
included do
|
16
|
-
initializer :add_indices_path do
|
17
|
-
paths.add "app/indices"
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
Rails::Engine.send :include, ThinkingSphinx::EnginePaths
|
@@ -1,8 +1,5 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
-
|
3
2
|
class ThinkingSphinx::Search::Query
|
4
|
-
DEFAULT_TOKEN = /[\p{Word}\\][\p{Word}\\@]+/
|
5
|
-
|
6
3
|
attr_reader :keywords, :conditions, :star
|
7
4
|
|
8
5
|
def initialize(keywords = '', conditions = {}, star = false)
|
@@ -20,24 +17,9 @@ class ThinkingSphinx::Search::Query
|
|
20
17
|
private
|
21
18
|
|
22
19
|
def star_keyword(keyword, key = nil)
|
23
|
-
|
24
|
-
|
25
|
-
end
|
20
|
+
return keyword.to_s unless star
|
21
|
+
return keyword.to_s if key.to_s == 'sphinx_internal_class_name'
|
26
22
|
|
27
|
-
|
28
|
-
keyword.gsub(/("#{token}(.*?#{token})?"|(?![!-])#{token})/u) do
|
29
|
-
pre, proper, post = $`, $&, $'
|
30
|
-
# E.g. "@foo", "/2", "~3", but not as part of a token
|
31
|
-
is_operator = pre.match(%r{\A(\W|^)[@~/]\Z}) ||
|
32
|
-
pre.match(%r{(\W|^)@\([^\)]*$})
|
33
|
-
# E.g. "foo bar", with quotes
|
34
|
-
is_quote = proper[/^".*"$/]
|
35
|
-
has_star = post[/\*$/] || pre[/^\*/]
|
36
|
-
if is_operator || is_quote || has_star
|
37
|
-
proper
|
38
|
-
else
|
39
|
-
"*#{proper}*"
|
40
|
-
end
|
41
|
-
end
|
23
|
+
ThinkingSphinx::Query.wildcard keyword, star
|
42
24
|
end
|
43
25
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
class ThinkingSphinx::Wildcard
|
2
|
+
DEFAULT_TOKEN = /\p{Word}+/
|
3
|
+
|
4
|
+
def self.call(query, pattern = DEFAULT_TOKEN)
|
5
|
+
new(query, pattern).call
|
6
|
+
end
|
7
|
+
|
8
|
+
def initialize(query, pattern = DEFAULT_TOKEN)
|
9
|
+
@query = query || ''
|
10
|
+
@pattern = pattern.is_a?(Regexp) ? pattern : DEFAULT_TOKEN
|
11
|
+
end
|
12
|
+
|
13
|
+
def call
|
14
|
+
query.gsub(/("#{pattern}(.*?#{pattern})?"|(?![!-])#{pattern})/u) do
|
15
|
+
pre, proper, post = $`, $&, $'
|
16
|
+
# E.g. "@foo", "/2", "~3", but not as part of a token pattern
|
17
|
+
is_operator = pre == '@' ||
|
18
|
+
pre.match(%r{([^\\]+|\A)[~/]\Z}) ||
|
19
|
+
pre.match(%r{(\W|^)@\([^\)]*$})
|
20
|
+
# E.g. "foo bar", with quotes
|
21
|
+
is_quote = proper[/^".*"$/]
|
22
|
+
has_star = post[/\*$/] || pre[/^\*/]
|
23
|
+
if is_operator || is_quote || has_star
|
24
|
+
proper
|
25
|
+
else
|
26
|
+
"*#{proper}*"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
attr_reader :query, :pattern
|
34
|
+
end
|
@@ -36,4 +36,17 @@ describe 'Searching by latitude and longitude', :live => true do
|
|
36
36
|
cities.first.geodist.should == 250326.906250
|
37
37
|
end
|
38
38
|
end
|
39
|
+
|
40
|
+
it "handles custom select clauses that refer to the distance" do
|
41
|
+
mel = City.create :name => 'Melbourne', :lat => -0.6599720, :lng => 2.530082
|
42
|
+
syd = City.create :name => 'Sydney', :lat => -0.5909679, :lng => 2.639131
|
43
|
+
bri = City.create :name => 'Brisbane', :lat => -0.4794031, :lng => 2.670838
|
44
|
+
index
|
45
|
+
|
46
|
+
City.search(
|
47
|
+
:geo => [-0.616241, 2.602712],
|
48
|
+
:with => {:geodist => 0.0..470_000.0},
|
49
|
+
:select => "*, geodist as custom_weight"
|
50
|
+
).to_a.should == [mel, syd]
|
51
|
+
end
|
39
52
|
end
|