thinking-sphinx 3.1.0 → 3.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
|