thinking-sphinx 3.0.3 → 3.0.4
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 +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
|