thinking-sphinx 3.0.3 → 3.0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +2 -1
- data/HISTORY +25 -0
- data/lib/thinking_sphinx.rb +1 -0
- data/lib/thinking_sphinx/active_record/association_proxy.rb +13 -55
- data/lib/thinking_sphinx/active_record/association_proxy/attribute_finder.rb +47 -0
- data/lib/thinking_sphinx/active_record/base.rb +16 -15
- data/lib/thinking_sphinx/active_record/callbacks/delete_callbacks.rb +1 -12
- data/lib/thinking_sphinx/active_record/database_adapters.rb +41 -42
- data/lib/thinking_sphinx/active_record/polymorpher.rb +7 -2
- data/lib/thinking_sphinx/active_record/property_query.rb +23 -19
- data/lib/thinking_sphinx/active_record/sql_builder.rb +108 -129
- data/lib/thinking_sphinx/active_record/sql_builder/clause_builder.rb +28 -0
- data/lib/thinking_sphinx/active_record/sql_builder/query.rb +43 -0
- data/lib/thinking_sphinx/active_record/sql_builder/statement.rb +110 -0
- data/lib/thinking_sphinx/active_record/sql_source.rb +143 -138
- data/lib/thinking_sphinx/capistrano.rb +11 -8
- data/lib/thinking_sphinx/configuration.rb +57 -35
- data/lib/thinking_sphinx/connection.rb +15 -6
- data/lib/thinking_sphinx/core.rb +1 -0
- data/lib/thinking_sphinx/core/index.rb +18 -10
- data/lib/thinking_sphinx/core/settings.rb +9 -0
- data/lib/thinking_sphinx/deletion.rb +48 -0
- data/lib/thinking_sphinx/errors.rb +7 -0
- data/lib/thinking_sphinx/excerpter.rb +1 -0
- data/lib/thinking_sphinx/facet_search.rb +42 -19
- data/lib/thinking_sphinx/masks/scopes_mask.rb +7 -0
- data/lib/thinking_sphinx/middlewares.rb +27 -33
- data/lib/thinking_sphinx/middlewares/active_record_translator.rb +18 -18
- data/lib/thinking_sphinx/middlewares/geographer.rb +49 -16
- data/lib/thinking_sphinx/middlewares/sphinxql.rb +128 -58
- data/lib/thinking_sphinx/panes/excerpts_pane.rb +7 -3
- data/lib/thinking_sphinx/rake_interface.rb +10 -0
- data/lib/thinking_sphinx/real_time.rb +7 -1
- data/lib/thinking_sphinx/real_time/attribute.rb +4 -0
- data/lib/thinking_sphinx/real_time/callbacks/real_time_callbacks.rb +14 -10
- data/lib/thinking_sphinx/real_time/index.rb +20 -12
- data/lib/thinking_sphinx/search/glaze.rb +5 -0
- data/lib/thinking_sphinx/search/query.rb +4 -8
- data/lib/thinking_sphinx/tasks.rb +8 -0
- data/spec/acceptance/excerpts_spec.rb +22 -0
- data/spec/acceptance/remove_deleted_records_spec.rb +10 -0
- data/spec/acceptance/searching_across_models_spec.rb +10 -0
- data/spec/acceptance/searching_with_filters_spec.rb +15 -0
- data/spec/acceptance/specifying_sql_spec.rb +3 -3
- data/spec/acceptance/sphinx_scopes_spec.rb +11 -0
- data/spec/internal/app/indices/product_index.rb +2 -0
- data/spec/internal/app/models/categorisation.rb +6 -0
- data/spec/internal/app/models/category.rb +3 -0
- data/spec/internal/app/models/product.rb +4 -1
- data/spec/internal/db/schema.rb +10 -0
- data/spec/thinking_sphinx/active_record/base_spec.rb +33 -0
- data/spec/thinking_sphinx/active_record/callbacks/delete_callbacks_spec.rb +4 -35
- data/spec/thinking_sphinx/active_record/sql_builder_spec.rb +5 -10
- data/spec/thinking_sphinx/active_record/sql_source_spec.rb +4 -3
- data/spec/thinking_sphinx/configuration_spec.rb +26 -6
- data/spec/thinking_sphinx/connection_spec.rb +4 -1
- data/spec/thinking_sphinx/deletion_spec.rb +76 -0
- data/spec/thinking_sphinx/facet_search_spec.rb +54 -5
- data/spec/thinking_sphinx/panes/excerpts_pane_spec.rb +4 -6
- data/spec/thinking_sphinx/rake_interface_spec.rb +35 -0
- data/spec/thinking_sphinx/real_time/callbacks/real_time_callbacks_spec.rb +68 -28
- data/spec/thinking_sphinx/search/glaze_spec.rb +19 -0
- data/spec/thinking_sphinx/search/query_spec.rb +39 -2
- data/thinking-sphinx.gemspec +2 -2
- metadata +31 -45
@@ -1,164 +1,143 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
module ThinkingSphinx
|
2
|
+
module ActiveRecord
|
3
|
+
class SQLBuilder
|
4
|
+
attr_reader :source
|
3
5
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
def sql_query
|
9
|
-
relation = model.unscoped
|
10
|
-
relation = relation.select pre_select + select_clause
|
11
|
-
relation = relation.where where_clause
|
12
|
-
relation = relation.group group_clause
|
13
|
-
relation = relation.order('NULL') if source.type == 'mysql'
|
14
|
-
relation = relation.joins associations.join_values
|
15
|
-
relation = relation.joins custom_joins.collect(&:to_s) if custom_joins.any?
|
16
|
-
|
17
|
-
relation.to_sql.gsub(/\n/, "\\\n")
|
18
|
-
end
|
19
|
-
|
20
|
-
def sql_query_range
|
21
|
-
return nil if source.disable_range?
|
6
|
+
def initialize(source)
|
7
|
+
@source = source
|
8
|
+
end
|
22
9
|
|
23
|
-
|
24
|
-
|
10
|
+
def sql_query
|
11
|
+
statement.to_relation.to_sql.gsub(/\n/, "\\\n")
|
12
|
+
end
|
25
13
|
|
26
|
-
|
27
|
-
|
28
|
-
|
14
|
+
def sql_query_range
|
15
|
+
return nil if source.disable_range?
|
16
|
+
statement.to_query_range_relation.to_sql
|
17
|
+
end
|
29
18
|
|
30
|
-
|
31
|
-
|
19
|
+
def sql_query_info
|
20
|
+
statement.to_query_info_relation.to_sql
|
21
|
+
end
|
32
22
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
end
|
23
|
+
def sql_query_pre
|
24
|
+
query.to_query
|
25
|
+
end
|
37
26
|
|
38
|
-
|
39
|
-
queries = []
|
27
|
+
private
|
40
28
|
|
41
|
-
|
42
|
-
|
29
|
+
def query
|
30
|
+
Query.new(self)
|
31
|
+
end
|
43
32
|
|
44
|
-
|
45
|
-
|
46
|
-
|
33
|
+
def statement
|
34
|
+
Statement.new(self)
|
35
|
+
end
|
47
36
|
|
48
|
-
|
49
|
-
|
37
|
+
def config
|
38
|
+
ThinkingSphinx::Configuration.instance
|
39
|
+
end
|
50
40
|
|
51
|
-
|
41
|
+
delegate :adapter, :model, :delta_processor, :to => :source
|
42
|
+
delegate :convert_nulls, :utf8_query_pre, :to => :adapter
|
43
|
+
def relation
|
44
|
+
model.unscoped
|
45
|
+
end
|
52
46
|
|
53
|
-
|
54
|
-
|
55
|
-
|
47
|
+
def base_join
|
48
|
+
@base_join ||= join_dependency_class.new model, [], initial_joins
|
49
|
+
end
|
56
50
|
|
57
|
-
|
58
|
-
|
59
|
-
|
51
|
+
def associations
|
52
|
+
@associations ||= ThinkingSphinx::ActiveRecord::Associations.new(model).tap do |assocs|
|
53
|
+
source.associations.reject(&:string?).each do |association|
|
54
|
+
assocs.add_join_to association.stack
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
60
58
|
|
61
|
-
|
62
|
-
|
63
|
-
|
59
|
+
def custom_joins
|
60
|
+
@custom_joins ||= source.associations.select(&:string?).collect(&:to_s)
|
61
|
+
end
|
64
62
|
|
65
|
-
|
66
|
-
|
67
|
-
|
63
|
+
def quote_column(column)
|
64
|
+
model.connection.quote_column_name(column)
|
65
|
+
end
|
68
66
|
|
69
|
-
|
70
|
-
|
71
|
-
source.associations.reject(&:string?).each do |association|
|
72
|
-
assocs.add_join_to association.stack
|
67
|
+
def quoted_primary_key
|
68
|
+
"#{model.quoted_table_name}.#{quote_column(source.primary_key)}"
|
73
69
|
end
|
74
|
-
end
|
75
|
-
end
|
76
70
|
|
77
|
-
|
78
|
-
|
79
|
-
|
71
|
+
def quoted_inheritance_column
|
72
|
+
"#{model.quoted_table_name}.#{quote_column(model.inheritance_column)}"
|
73
|
+
end
|
80
74
|
|
81
|
-
|
82
|
-
|
83
|
-
|
75
|
+
def pre_select
|
76
|
+
('SQL_NO_CACHE ' if source.type == 'mysql').to_s
|
77
|
+
end
|
84
78
|
|
85
|
-
|
86
|
-
|
87
|
-
|
79
|
+
def document_id
|
80
|
+
quoted_alias = quote_column source.primary_key
|
81
|
+
"#{quoted_primary_key} * #{config.indices.count} + #{source.offset} AS #{quoted_alias}"
|
82
|
+
end
|
88
83
|
|
89
|
-
|
90
|
-
|
91
|
-
|
84
|
+
def reversed_document_id
|
85
|
+
"($id - #{source.offset}) / #{config.indices.count}"
|
86
|
+
end
|
92
87
|
|
93
|
-
|
94
|
-
|
95
|
-
|
88
|
+
def attribute_presenters
|
89
|
+
@attribute_presenters ||= property_sql_presenters_for(source.attributes)
|
90
|
+
end
|
96
91
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
end
|
92
|
+
def field_presenters
|
93
|
+
@field_presenters ||= property_sql_presenters_for(source.fields)
|
94
|
+
end
|
101
95
|
|
102
|
-
|
103
|
-
|
104
|
-
|
96
|
+
def property_sql_presenters_for(fields)
|
97
|
+
fields.collect { |field| property_sql_presenter_for(field) }
|
98
|
+
end
|
105
99
|
|
106
|
-
|
107
|
-
@attribute_presenters ||= begin
|
108
|
-
source.attributes.collect { |attribute|
|
100
|
+
def property_sql_presenter_for(field)
|
109
101
|
ThinkingSphinx::ActiveRecord::PropertySQLPresenter.new(
|
110
|
-
|
102
|
+
field, source.adapter, associations
|
111
103
|
)
|
112
|
-
|
113
|
-
end
|
114
|
-
end
|
115
|
-
|
116
|
-
def field_presenters
|
117
|
-
@field_presenters ||= source.fields.collect { |field|
|
118
|
-
ThinkingSphinx::ActiveRecord::PropertySQLPresenter.new(
|
119
|
-
field, source.adapter, associations
|
120
|
-
)
|
121
|
-
}
|
122
|
-
end
|
123
|
-
|
124
|
-
def select_clause
|
125
|
-
(
|
126
|
-
[document_id] +
|
127
|
-
field_presenters.collect(&:to_select) +
|
128
|
-
attribute_presenters.collect(&:to_select)
|
129
|
-
).compact.join(', ')
|
130
|
-
end
|
104
|
+
end
|
131
105
|
|
132
|
-
|
133
|
-
|
106
|
+
def inheritance_column_condition
|
107
|
+
"#{quoted_inheritance_column} = '#{model_name}'"
|
108
|
+
end
|
134
109
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
110
|
+
def range_condition
|
111
|
+
condition = []
|
112
|
+
condition << "#{quoted_primary_key} BETWEEN $start AND $end" unless source.disable_range?
|
113
|
+
condition += source.conditions
|
114
|
+
condition
|
115
|
+
end
|
139
116
|
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
end
|
117
|
+
def presenters_to_group(presenters)
|
118
|
+
presenters.collect(&:to_group)
|
119
|
+
end
|
144
120
|
|
145
|
-
|
146
|
-
|
121
|
+
def presenters_to_select(presenters)
|
122
|
+
presenters.collect(&:to_select)
|
123
|
+
end
|
147
124
|
|
148
|
-
|
149
|
-
|
125
|
+
def groupings
|
126
|
+
groupings = source.groupings
|
127
|
+
if model.column_names.include?(model.inheritance_column)
|
128
|
+
groupings << quoted_inheritance_column
|
129
|
+
end
|
130
|
+
groupings
|
131
|
+
end
|
150
132
|
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
133
|
+
def model_name
|
134
|
+
klass = model.name
|
135
|
+
klass = klass.demodulize unless model.store_full_sti_class
|
136
|
+
klass
|
137
|
+
end
|
155
138
|
end
|
156
|
-
|
157
|
-
(
|
158
|
-
[quoted_primary_key] +
|
159
|
-
field_presenters.collect(&:to_group).compact +
|
160
|
-
attribute_presenters.collect(&:to_group).compact +
|
161
|
-
source.groupings + internal_groupings
|
162
|
-
).join(', ')
|
163
139
|
end
|
164
140
|
end
|
141
|
+
|
142
|
+
require 'thinking_sphinx/active_record/sql_builder/statement'
|
143
|
+
require 'thinking_sphinx/active_record/sql_builder/query'
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module ThinkingSphinx
|
2
|
+
module ActiveRecord
|
3
|
+
class SQLBuilder::ClauseBuilder
|
4
|
+
def initialize(first_element)
|
5
|
+
@first_element = first_element
|
6
|
+
end
|
7
|
+
|
8
|
+
def compose(*additions)
|
9
|
+
additions.each &method(:add_clause)
|
10
|
+
self
|
11
|
+
end
|
12
|
+
|
13
|
+
def add_clause(clause)
|
14
|
+
self.clauses += Array(clause)
|
15
|
+
end
|
16
|
+
|
17
|
+
def separated(by = ', ')
|
18
|
+
clauses.flatten.compact.join(by)
|
19
|
+
end
|
20
|
+
|
21
|
+
protected
|
22
|
+
attr_accessor :clauses
|
23
|
+
def clauses
|
24
|
+
@clauses ||= [@first_element]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module ThinkingSphinx
|
2
|
+
module ActiveRecord
|
3
|
+
class SQLBuilder::Query
|
4
|
+
def initialize(report)
|
5
|
+
self.report = report
|
6
|
+
self.scope = []
|
7
|
+
end
|
8
|
+
|
9
|
+
def to_query
|
10
|
+
filter_by_query_pre
|
11
|
+
|
12
|
+
scope.compact
|
13
|
+
end
|
14
|
+
|
15
|
+
protected
|
16
|
+
attr_accessor :report, :scope
|
17
|
+
|
18
|
+
def filter_by_query_pre
|
19
|
+
scope_by_delta_processor
|
20
|
+
scope_by_session
|
21
|
+
scope_by_utf8
|
22
|
+
end
|
23
|
+
|
24
|
+
def scope_by_delta_processor
|
25
|
+
self.scope << delta_processor.reset_query if delta_processor && !source.delta?
|
26
|
+
end
|
27
|
+
|
28
|
+
def scope_by_session
|
29
|
+
if max_len = source.options[:group_concat_max_len]
|
30
|
+
self.scope << "SET SESSION group_concat_max_len = #{max_len}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def scope_by_utf8
|
35
|
+
self.scope += utf8_query_pre if source.options[:utf8?]
|
36
|
+
end
|
37
|
+
|
38
|
+
def method_missing(*args, &block)
|
39
|
+
report.send *args, &block
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
require 'thinking_sphinx/active_record/sql_builder/clause_builder'
|
2
|
+
|
3
|
+
module ThinkingSphinx
|
4
|
+
module ActiveRecord
|
5
|
+
class SQLBuilder::Statement
|
6
|
+
def initialize(report)
|
7
|
+
self.report = report
|
8
|
+
self.scope = relation
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_relation
|
12
|
+
filter_by_scopes
|
13
|
+
|
14
|
+
scope
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_query_range_relation
|
18
|
+
filter_by_query_range
|
19
|
+
|
20
|
+
scope
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_query_info_relation
|
24
|
+
filter_by_query_info
|
25
|
+
|
26
|
+
scope
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_query_pre
|
30
|
+
filter_by_query_pre
|
31
|
+
|
32
|
+
scope
|
33
|
+
end
|
34
|
+
|
35
|
+
protected
|
36
|
+
attr_accessor :report, :scope
|
37
|
+
|
38
|
+
def filter_by_query_range
|
39
|
+
minimum = convert_nulls "MIN(#{quoted_primary_key})", 1
|
40
|
+
maximum = convert_nulls "MAX(#{quoted_primary_key})", 1
|
41
|
+
|
42
|
+
self.scope = scope.select("#{minimum}, #{maximum}").where(where_clause(true))
|
43
|
+
end
|
44
|
+
|
45
|
+
def filter_by_query_info
|
46
|
+
self.scope = scope.where("#{quoted_primary_key} = #{reversed_document_id}")
|
47
|
+
end
|
48
|
+
|
49
|
+
def filter_by_scopes
|
50
|
+
scope_by_select
|
51
|
+
scope_by_where_clause
|
52
|
+
scope_by_group_clause
|
53
|
+
scope_by_joins
|
54
|
+
scope_by_custom_joins
|
55
|
+
scope_by_order
|
56
|
+
end
|
57
|
+
|
58
|
+
def scope_by_select
|
59
|
+
self.scope = scope.select(pre_select + select_clause)
|
60
|
+
end
|
61
|
+
|
62
|
+
def scope_by_where_clause
|
63
|
+
self.scope = scope.where where_clause
|
64
|
+
end
|
65
|
+
|
66
|
+
def scope_by_group_clause
|
67
|
+
self.scope = scope.group(group_clause)
|
68
|
+
end
|
69
|
+
|
70
|
+
def scope_by_joins
|
71
|
+
self.scope = scope.joins(associations.join_values)
|
72
|
+
end
|
73
|
+
|
74
|
+
def scope_by_custom_joins
|
75
|
+
self.scope = scope.joins(custom_joins) if custom_joins.any?
|
76
|
+
end
|
77
|
+
|
78
|
+
def scope_by_order
|
79
|
+
self.scope = scope.order('NULL') if source.type == 'mysql'
|
80
|
+
end
|
81
|
+
|
82
|
+
def method_missing(*args, &block)
|
83
|
+
report.send *args, &block
|
84
|
+
end
|
85
|
+
|
86
|
+
def select_clause
|
87
|
+
SQLBuilder::ClauseBuilder.new(document_id).compose(
|
88
|
+
presenters_to_select(field_presenters),
|
89
|
+
presenters_to_select(attribute_presenters)
|
90
|
+
).separated
|
91
|
+
end
|
92
|
+
|
93
|
+
def where_clause(for_range = false)
|
94
|
+
builder = SQLBuilder::ClauseBuilder.new(nil)
|
95
|
+
builder.add_clause inheritance_column_condition unless model.descends_from_active_record?
|
96
|
+
builder.add_clause delta_processor.clause(source.delta?) if delta_processor
|
97
|
+
builder.add_clause range_condition unless for_range
|
98
|
+
builder.separated(' AND ')
|
99
|
+
end
|
100
|
+
|
101
|
+
def group_clause
|
102
|
+
SQLBuilder::ClauseBuilder.new(quoted_primary_key).compose(
|
103
|
+
presenters_to_group(field_presenters),
|
104
|
+
presenters_to_group(attribute_presenters),
|
105
|
+
groupings
|
106
|
+
).separated
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|