thinking-sphinx 3.1.0 → 3.1.1
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.
- checksums.yaml +4 -4
- data/.gitignore +2 -1
- data/.travis.yml +4 -7
- data/HISTORY +27 -0
- data/README.textile +38 -218
- data/gemfiles/rails_3_2.gemfile +2 -3
- data/gemfiles/rails_4_0.gemfile +2 -3
- data/gemfiles/rails_4_1.gemfile +2 -3
- data/lib/thinking_sphinx.rb +1 -0
- data/lib/thinking_sphinx/active_record.rb +1 -0
- data/lib/thinking_sphinx/active_record/association_proxy.rb +1 -0
- data/lib/thinking_sphinx/active_record/association_proxy/attribute_finder.rb +5 -10
- data/lib/thinking_sphinx/active_record/association_proxy/attribute_matcher.rb +38 -0
- data/lib/thinking_sphinx/active_record/attribute/type.rb +19 -8
- data/lib/thinking_sphinx/active_record/base.rb +3 -1
- data/lib/thinking_sphinx/active_record/callbacks/delta_callbacks.rb +1 -1
- data/lib/thinking_sphinx/active_record/callbacks/update_callbacks.rb +4 -1
- data/lib/thinking_sphinx/active_record/index.rb +4 -4
- data/lib/thinking_sphinx/active_record/property_query.rb +57 -27
- data/lib/thinking_sphinx/active_record/simple_many_query.rb +35 -0
- data/lib/thinking_sphinx/capistrano/v3.rb +11 -10
- data/lib/thinking_sphinx/configuration.rb +23 -6
- data/lib/thinking_sphinx/connection.rb +8 -9
- data/lib/thinking_sphinx/errors.rb +7 -2
- data/lib/thinking_sphinx/facet.rb +2 -2
- data/lib/thinking_sphinx/facet_search.rb +4 -2
- data/lib/thinking_sphinx/logger.rb +7 -0
- data/lib/thinking_sphinx/masks/group_enumerators_mask.rb +4 -4
- data/lib/thinking_sphinx/middlewares/inquirer.rb +2 -2
- data/lib/thinking_sphinx/middlewares/sphinxql.rb +6 -2
- data/lib/thinking_sphinx/middlewares/stale_id_filter.rb +1 -1
- data/lib/thinking_sphinx/real_time/callbacks/real_time_callbacks.rb +6 -1
- data/lib/thinking_sphinx/real_time/property.rb +2 -1
- data/lib/thinking_sphinx/real_time/transcriber.rb +7 -3
- data/lib/thinking_sphinx/search.rb +14 -4
- data/lib/thinking_sphinx/search/context.rb +0 -6
- data/lib/thinking_sphinx/test.rb +11 -2
- data/lib/thinking_sphinx/wildcard.rb +7 -1
- data/spec/acceptance/association_scoping_spec.rb +55 -15
- data/spec/acceptance/geosearching_spec.rb +8 -2
- data/spec/acceptance/real_time_updates_spec.rb +9 -0
- data/spec/acceptance/specifying_sql_spec.rb +31 -17
- data/spec/internal/app/indices/car_index.rb +5 -0
- data/spec/internal/app/models/car.rb +5 -0
- data/spec/internal/app/models/category.rb +2 -1
- data/spec/internal/app/models/manufacturer.rb +3 -0
- data/spec/internal/db/schema.rb +9 -0
- data/spec/thinking_sphinx/active_record/attribute/type_spec.rb +7 -0
- data/spec/thinking_sphinx/active_record/base_spec.rb +17 -0
- data/spec/thinking_sphinx/active_record/callbacks/update_callbacks_spec.rb +2 -1
- data/spec/thinking_sphinx/configuration_spec.rb +40 -2
- data/spec/thinking_sphinx/errors_spec.rb +21 -0
- data/spec/thinking_sphinx/facet_search_spec.rb +6 -6
- data/spec/thinking_sphinx/middlewares/inquirer_spec.rb +0 -4
- data/spec/thinking_sphinx/middlewares/sphinxql_spec.rb +25 -0
- data/spec/thinking_sphinx/middlewares/stale_id_filter_spec.rb +2 -2
- data/spec/thinking_sphinx/real_time/callbacks/real_time_callbacks_spec.rb +2 -1
- data/spec/thinking_sphinx/search_spec.rb +56 -0
- data/spec/thinking_sphinx/wildcard_spec.rb +5 -0
- data/thinking-sphinx.gemspec +2 -2
- metadata +40 -32
- data/sketchpad.rb +0 -58
- data/spec/internal/log/.gitignore +0 -1
@@ -0,0 +1,38 @@
|
|
1
|
+
class ThinkingSphinx::ActiveRecord::AssociationProxy::AttributeMatcher
|
2
|
+
def initialize(attribute, foreign_key)
|
3
|
+
@attribute, @foreign_key = attribute, foreign_key.to_s
|
4
|
+
end
|
5
|
+
|
6
|
+
def matches?
|
7
|
+
return false if many?
|
8
|
+
|
9
|
+
column_name_matches? || attribute_name_matches? || multi_singular_match?
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
attr_reader :attribute, :foreign_key
|
15
|
+
|
16
|
+
delegate :name, :multi?, :to => :attribute
|
17
|
+
|
18
|
+
def attribute_name_matches?
|
19
|
+
name == foreign_key
|
20
|
+
end
|
21
|
+
|
22
|
+
def column_name_matches?
|
23
|
+
column.__name.to_s == foreign_key
|
24
|
+
end
|
25
|
+
|
26
|
+
def column
|
27
|
+
attribute.respond_to?(:columns) ? attribute.columns.first :
|
28
|
+
attribute.column
|
29
|
+
end
|
30
|
+
|
31
|
+
def many?
|
32
|
+
attribute.respond_to?(:columns) && attribute.columns.many?
|
33
|
+
end
|
34
|
+
|
35
|
+
def multi_singular_match?
|
36
|
+
multi? && name.singularize == foreign_key
|
37
|
+
end
|
38
|
+
end
|
@@ -40,6 +40,20 @@ class ThinkingSphinx::ActiveRecord::Attribute::Type
|
|
40
40
|
end
|
41
41
|
end
|
42
42
|
|
43
|
+
def big_integer?
|
44
|
+
database_column.type == :integer && database_column.sql_type[/bigint/i]
|
45
|
+
end
|
46
|
+
|
47
|
+
def column_name
|
48
|
+
attribute.columns.first.__name.to_s
|
49
|
+
end
|
50
|
+
|
51
|
+
def database_column
|
52
|
+
@database_column ||= klass.columns.detect { |db_column|
|
53
|
+
db_column.name == column_name
|
54
|
+
}
|
55
|
+
end
|
56
|
+
|
43
57
|
def klass
|
44
58
|
@klass ||= associations.any? ? associations.last.klass : model
|
45
59
|
end
|
@@ -57,15 +71,12 @@ class ThinkingSphinx::ActiveRecord::Attribute::Type
|
|
57
71
|
end
|
58
72
|
|
59
73
|
def type_from_database
|
60
|
-
|
61
|
-
|
62
|
-
}
|
74
|
+
raise ThinkingSphinx::MissingColumnError,
|
75
|
+
"column #{column_name} does not exist" if database_column.nil?
|
63
76
|
|
64
|
-
|
65
|
-
return :bigint
|
66
|
-
end
|
77
|
+
return :bigint if big_integer?
|
67
78
|
|
68
|
-
case
|
79
|
+
case database_column.type
|
69
80
|
when :datetime, :date
|
70
81
|
:timestamp
|
71
82
|
when :text
|
@@ -73,7 +84,7 @@ class ThinkingSphinx::ActiveRecord::Attribute::Type
|
|
73
84
|
when :decimal
|
74
85
|
:float
|
75
86
|
else
|
76
|
-
|
87
|
+
database_column.type
|
77
88
|
end
|
78
89
|
end
|
79
90
|
end
|
@@ -50,7 +50,9 @@ module ThinkingSphinx::ActiveRecord::Base
|
|
50
50
|
'You cannot search with Sphinx through ActiveRecord scopes'
|
51
51
|
end
|
52
52
|
|
53
|
-
merger.merge! nil, :classes => [self]
|
53
|
+
result = merger.merge! nil, :classes => [self]
|
54
|
+
result.populate if result.options[:populate]
|
55
|
+
result
|
54
56
|
end
|
55
57
|
end
|
56
58
|
end
|
@@ -28,7 +28,10 @@ class ThinkingSphinx::ActiveRecord::Callbacks::UpdateCallbacks <
|
|
28
28
|
end
|
29
29
|
|
30
30
|
def indices
|
31
|
-
@indices ||=
|
31
|
+
@indices ||= begin
|
32
|
+
all = configuration.indices_for_references(reference)
|
33
|
+
all.reject { |index| index.type == 'rt' }
|
34
|
+
end
|
32
35
|
end
|
33
36
|
|
34
37
|
def reference
|
@@ -12,6 +12,10 @@ class ThinkingSphinx::ActiveRecord::Index < Riddle::Configuration::Index
|
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
15
|
+
def attributes
|
16
|
+
sources.collect(&:attributes).flatten
|
17
|
+
end
|
18
|
+
|
15
19
|
def delta?
|
16
20
|
@options[:delta?]
|
17
21
|
end
|
@@ -40,10 +44,6 @@ class ThinkingSphinx::ActiveRecord::Index < Riddle::Configuration::Index
|
|
40
44
|
adapter_for(model)
|
41
45
|
end
|
42
46
|
|
43
|
-
def attributes
|
44
|
-
sources.collect(&:attributes).flatten
|
45
|
-
end
|
46
|
-
|
47
47
|
def fields
|
48
48
|
sources.collect(&:fields).flatten
|
49
49
|
end
|
@@ -4,26 +4,28 @@ class ThinkingSphinx::ActiveRecord::PropertyQuery
|
|
4
4
|
end
|
5
5
|
|
6
6
|
def to_s
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
7
|
+
if unsafe_habtm_column?
|
8
|
+
raise <<-MESSAGE
|
9
|
+
Source queries cannot be used with HABTM joins if they use anything beyond the
|
10
|
+
primary key.
|
11
|
+
MESSAGE
|
12
|
+
end
|
13
13
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
14
|
+
if safe_habtm_column?
|
15
|
+
ThinkingSphinx::ActiveRecord::SimpleManyQuery.new(
|
16
|
+
property, source, type
|
17
|
+
).to_s
|
18
18
|
else
|
19
|
-
queries
|
20
|
-
queries << range_sql if ranged?
|
19
|
+
"#{identifier} from #{source_type}; #{queries.join('; ')}"
|
21
20
|
end
|
22
|
-
queries
|
23
21
|
end
|
24
22
|
|
23
|
+
private
|
24
|
+
|
25
25
|
attr_reader :property, :source, :type
|
26
26
|
|
27
|
+
delegate :unscoped, :to => :base_association_class, :prefix => true
|
28
|
+
|
27
29
|
def base_association
|
28
30
|
reflections.first
|
29
31
|
end
|
@@ -31,7 +33,6 @@ class ThinkingSphinx::ActiveRecord::PropertyQuery
|
|
31
33
|
def base_association_class
|
32
34
|
base_association.klass
|
33
35
|
end
|
34
|
-
delegate :unscoped, :to => :base_association_class, :prefix => true
|
35
36
|
|
36
37
|
def column
|
37
38
|
@column ||= property.columns.first
|
@@ -43,17 +44,8 @@ class ThinkingSphinx::ActiveRecord::PropertyQuery
|
|
43
44
|
[reflection.through_reflection, reflection.source_reflection]
|
44
45
|
end
|
45
46
|
|
46
|
-
def
|
47
|
-
|
48
|
-
base = source.model
|
49
|
-
|
50
|
-
column.__stack.collect { |key|
|
51
|
-
reflection = base.reflections[key]
|
52
|
-
base = reflection.klass
|
53
|
-
|
54
|
-
extend_reflection reflection
|
55
|
-
}.flatten
|
56
|
-
end
|
47
|
+
def identifier
|
48
|
+
[type, property.name].compact.join(' ')
|
57
49
|
end
|
58
50
|
|
59
51
|
def joins
|
@@ -68,10 +60,25 @@ class ThinkingSphinx::ActiveRecord::PropertyQuery
|
|
68
60
|
end
|
69
61
|
end
|
70
62
|
|
63
|
+
def macros
|
64
|
+
reflections.collect &:macro
|
65
|
+
end
|
66
|
+
|
71
67
|
def offset
|
72
68
|
"* #{ThinkingSphinx::Configuration.instance.indices.count} + #{source.offset}"
|
73
69
|
end
|
74
70
|
|
71
|
+
def queries
|
72
|
+
queries = []
|
73
|
+
if column.string?
|
74
|
+
queries << column.__name.strip.gsub(/\n/, "\\\n")
|
75
|
+
else
|
76
|
+
queries << to_sql
|
77
|
+
queries << range_sql if ranged?
|
78
|
+
end
|
79
|
+
queries
|
80
|
+
end
|
81
|
+
|
75
82
|
def quoted_foreign_key
|
76
83
|
quote_with_table(base_association_class.table_name, base_association.foreign_key)
|
77
84
|
end
|
@@ -98,6 +105,23 @@ class ThinkingSphinx::ActiveRecord::PropertyQuery
|
|
98
105
|
).to_sql
|
99
106
|
end
|
100
107
|
|
108
|
+
def reflections
|
109
|
+
@reflections ||= begin
|
110
|
+
base = source.model
|
111
|
+
|
112
|
+
column.__stack.collect { |key|
|
113
|
+
reflection = base.reflections[key]
|
114
|
+
base = reflection.klass
|
115
|
+
|
116
|
+
extend_reflection reflection
|
117
|
+
}.flatten
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def safe_habtm_column?
|
122
|
+
macros == [:has_and_belongs_to_many] && column.__name == :id
|
123
|
+
end
|
124
|
+
|
101
125
|
def source_type
|
102
126
|
property.source_type.to_s.dasherize
|
103
127
|
end
|
@@ -105,12 +129,18 @@ class ThinkingSphinx::ActiveRecord::PropertyQuery
|
|
105
129
|
def to_sql
|
106
130
|
raise "Could not determine SQL for MVA" if reflections.empty?
|
107
131
|
|
108
|
-
relation = base_association_class_unscoped.select("#{quoted_foreign_key} #{offset} AS #{quote_column('id')}, #{quoted_primary_key} AS #{quote_column(property.name)}"
|
109
|
-
)
|
132
|
+
relation = base_association_class_unscoped.select("#{quoted_foreign_key} #{offset} AS #{quote_column('id')}, #{quoted_primary_key} AS #{quote_column(property.name)}")
|
110
133
|
relation = relation.joins(joins) if joins.present?
|
111
134
|
relation = relation.where("#{quoted_foreign_key} BETWEEN $start AND $end") if ranged?
|
135
|
+
relation = relation.where("#{quoted_foreign_key} IS NOT NULL")
|
112
136
|
relation = relation.order("#{quoted_foreign_key} ASC") if type.nil?
|
113
137
|
|
114
138
|
relation.to_sql
|
115
139
|
end
|
140
|
+
|
141
|
+
def unsafe_habtm_column?
|
142
|
+
macros.include?(:has_and_belongs_to_many) && (
|
143
|
+
macros.length > 1 || column.__name != :id
|
144
|
+
)
|
145
|
+
end
|
116
146
|
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
class ThinkingSphinx::ActiveRecord::SimpleManyQuery <
|
2
|
+
ThinkingSphinx::ActiveRecord::PropertyQuery
|
3
|
+
|
4
|
+
def to_s
|
5
|
+
"#{identifier} from #{source_type}; #{queries.join('; ')}"
|
6
|
+
end
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def reflection
|
11
|
+
@reflection ||= source.model.reflections[column.__stack.first]
|
12
|
+
end
|
13
|
+
|
14
|
+
def quoted_foreign_key
|
15
|
+
quote_with_table reflection.join_table, reflection.foreign_key
|
16
|
+
end
|
17
|
+
|
18
|
+
def quoted_primary_key
|
19
|
+
quote_with_table reflection.join_table, reflection.association_foreign_key
|
20
|
+
end
|
21
|
+
|
22
|
+
def range_sql
|
23
|
+
"SELECT MIN(#{quoted_foreign_key}), MAX(#{quoted_foreign_key}) FROM #{quote_column reflection.join_table}"
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_sql
|
27
|
+
selects = [
|
28
|
+
"#{quoted_foreign_key} #{offset} AS #{quote_column('id')}",
|
29
|
+
"#{quoted_primary_key} AS #{quote_column(property.name)}"
|
30
|
+
]
|
31
|
+
sql = "SELECT #{selects.join(', ')} FROM #{quote_column reflection.join_table}"
|
32
|
+
sql += " WHERE (#{quoted_foreign_key} BETWEEN $start AND $end)" if ranged?
|
33
|
+
sql
|
34
|
+
end
|
35
|
+
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
namespace :load do
|
2
2
|
task :defaults do
|
3
3
|
set :thinking_sphinx_roles, :db
|
4
|
+
set :thinking_sphinx_rails_env, -> { fetch(:stage) }
|
4
5
|
end
|
5
6
|
end
|
6
7
|
|
@@ -12,7 +13,7 @@ if you alter the structure of your indexes.
|
|
12
13
|
task :rebuild do
|
13
14
|
on roles fetch(:thinking_sphinx_roles) do
|
14
15
|
within current_path do
|
15
|
-
with rails_env: fetch(:
|
16
|
+
with rails_env: fetch(:thinking_sphinx_rails_env) do
|
16
17
|
execute :rake, "ts:rebuild"
|
17
18
|
end
|
18
19
|
end
|
@@ -21,9 +22,9 @@ if you alter the structure of your indexes.
|
|
21
22
|
|
22
23
|
desc 'Stop Sphinx, clear Sphinx index files, generate configuration file, start Sphinx, repopulate all data.'
|
23
24
|
task :regenerate do
|
24
|
-
on roles fetch(:
|
25
|
+
on roles fetch(:thinking_sphinx_roles) do
|
25
26
|
within current_path do
|
26
|
-
with rails_env: fetch(:
|
27
|
+
with rails_env: fetch(:thinking_sphinx_rails_env) do
|
27
28
|
execute :rake, 'ts:regenerate'
|
28
29
|
end
|
29
30
|
end
|
@@ -34,7 +35,7 @@ if you alter the structure of your indexes.
|
|
34
35
|
task :index do
|
35
36
|
on roles fetch(:thinking_sphinx_roles) do
|
36
37
|
within current_path do
|
37
|
-
with rails_env: fetch(:
|
38
|
+
with rails_env: fetch(:thinking_sphinx_rails_env) do
|
38
39
|
execute :rake, 'ts:index'
|
39
40
|
end
|
40
41
|
end
|
@@ -43,9 +44,9 @@ if you alter the structure of your indexes.
|
|
43
44
|
|
44
45
|
desc 'Generate Sphinx indexes into the shared path.'
|
45
46
|
task :generate do
|
46
|
-
on roles fetch(:
|
47
|
+
on roles fetch(:thinking_sphinx_roles) do
|
47
48
|
within current_path do
|
48
|
-
with rails_env: fetch(:
|
49
|
+
with rails_env: fetch(:thinking_sphinx_rails_env) do
|
49
50
|
execute :rake, 'ts:generate'
|
50
51
|
end
|
51
52
|
end
|
@@ -56,7 +57,7 @@ if you alter the structure of your indexes.
|
|
56
57
|
task :restart do
|
57
58
|
on roles fetch(:thinking_sphinx_roles) do
|
58
59
|
within current_path do
|
59
|
-
with rails_env: fetch(:
|
60
|
+
with rails_env: fetch(:thinking_sphinx_rails_env) do
|
60
61
|
%w(stop configure start).each do |task|
|
61
62
|
execute :rake, "ts:#{task}"
|
62
63
|
end
|
@@ -69,7 +70,7 @@ if you alter the structure of your indexes.
|
|
69
70
|
task :start do
|
70
71
|
on roles fetch(:thinking_sphinx_roles) do
|
71
72
|
within current_path do
|
72
|
-
with rails_env: fetch(:
|
73
|
+
with rails_env: fetch(:thinking_sphinx_rails_env) do
|
73
74
|
execute :rake, 'ts:start'
|
74
75
|
end
|
75
76
|
end
|
@@ -81,7 +82,7 @@ if you alter the structure of your indexes.
|
|
81
82
|
task :configure do
|
82
83
|
on roles fetch(:thinking_sphinx_roles) do
|
83
84
|
within current_path do
|
84
|
-
with rails_env: fetch(:
|
85
|
+
with rails_env: fetch(:thinking_sphinx_rails_env) do
|
85
86
|
execute :rake, 'ts:configure'
|
86
87
|
end
|
87
88
|
end
|
@@ -92,7 +93,7 @@ if you alter the structure of your indexes.
|
|
92
93
|
task :stop do
|
93
94
|
on roles fetch(:thinking_sphinx_roles) do
|
94
95
|
within current_path do
|
95
|
-
with rails_env: fetch(:
|
96
|
+
with rails_env: fetch(:thinking_sphinx_rails_env) do
|
96
97
|
execute :rake, 'ts:stop'
|
97
98
|
end
|
98
99
|
end
|
@@ -74,7 +74,9 @@ class ThinkingSphinx::Configuration < Riddle::Configuration
|
|
74
74
|
end
|
75
75
|
end
|
76
76
|
|
77
|
-
|
77
|
+
if settings['distributed_indices'].nil? || settings['distributed_indices']
|
78
|
+
ThinkingSphinx::Configuration::DistributedIndices.new(indices).reconcile
|
79
|
+
end
|
78
80
|
|
79
81
|
@preloaded_indices = true
|
80
82
|
end
|
@@ -117,13 +119,18 @@ class ThinkingSphinx::Configuration < Riddle::Configuration
|
|
117
119
|
end
|
118
120
|
|
119
121
|
def log_root
|
120
|
-
|
122
|
+
real_path 'log'
|
121
123
|
end
|
122
124
|
|
123
125
|
def framework_root
|
124
126
|
Pathname.new(framework.root)
|
125
127
|
end
|
126
128
|
|
129
|
+
def real_path(*arguments)
|
130
|
+
path = framework_root.join(*arguments)
|
131
|
+
path.exist? ? path.realpath : path
|
132
|
+
end
|
133
|
+
|
127
134
|
def settings_to_hash
|
128
135
|
contents = YAML.load(ERB.new(File.read(settings_file)).result)
|
129
136
|
contents && contents[environment] || {}
|
@@ -142,7 +149,12 @@ class ThinkingSphinx::Configuration < Riddle::Configuration
|
|
142
149
|
@indices_location = settings['indices_location'] || framework_root.join(
|
143
150
|
'db', 'sphinx', environment
|
144
151
|
).to_s
|
145
|
-
@version = settings['version'] || '2.
|
152
|
+
@version = settings['version'] || '2.1.4'
|
153
|
+
|
154
|
+
if settings['common_sphinx_configuration']
|
155
|
+
common.common_sphinx_configuration = true
|
156
|
+
indexer.common_sphinx_configuration = true
|
157
|
+
end
|
146
158
|
|
147
159
|
configure_searchd
|
148
160
|
|
@@ -152,12 +164,17 @@ class ThinkingSphinx::Configuration < Riddle::Configuration
|
|
152
164
|
end
|
153
165
|
|
154
166
|
def tmp_path
|
155
|
-
|
156
|
-
|
167
|
+
real_path 'tmp'
|
168
|
+
end
|
169
|
+
|
170
|
+
def sphinx_sections
|
171
|
+
sections = [indexer, searchd]
|
172
|
+
sections.unshift common if settings['common_sphinx_configuration']
|
173
|
+
sections
|
157
174
|
end
|
158
175
|
|
159
176
|
def apply_sphinx_settings!
|
160
|
-
|
177
|
+
sphinx_sections.each do |object|
|
161
178
|
settings.each do |key, value|
|
162
179
|
next unless object.class.settings.include?(key.to_sym)
|
163
180
|
|