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.
Files changed (66) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +2 -1
  3. data/HISTORY +25 -0
  4. data/lib/thinking_sphinx.rb +1 -0
  5. data/lib/thinking_sphinx/active_record/association_proxy.rb +13 -55
  6. data/lib/thinking_sphinx/active_record/association_proxy/attribute_finder.rb +47 -0
  7. data/lib/thinking_sphinx/active_record/base.rb +16 -15
  8. data/lib/thinking_sphinx/active_record/callbacks/delete_callbacks.rb +1 -12
  9. data/lib/thinking_sphinx/active_record/database_adapters.rb +41 -42
  10. data/lib/thinking_sphinx/active_record/polymorpher.rb +7 -2
  11. data/lib/thinking_sphinx/active_record/property_query.rb +23 -19
  12. data/lib/thinking_sphinx/active_record/sql_builder.rb +108 -129
  13. data/lib/thinking_sphinx/active_record/sql_builder/clause_builder.rb +28 -0
  14. data/lib/thinking_sphinx/active_record/sql_builder/query.rb +43 -0
  15. data/lib/thinking_sphinx/active_record/sql_builder/statement.rb +110 -0
  16. data/lib/thinking_sphinx/active_record/sql_source.rb +143 -138
  17. data/lib/thinking_sphinx/capistrano.rb +11 -8
  18. data/lib/thinking_sphinx/configuration.rb +57 -35
  19. data/lib/thinking_sphinx/connection.rb +15 -6
  20. data/lib/thinking_sphinx/core.rb +1 -0
  21. data/lib/thinking_sphinx/core/index.rb +18 -10
  22. data/lib/thinking_sphinx/core/settings.rb +9 -0
  23. data/lib/thinking_sphinx/deletion.rb +48 -0
  24. data/lib/thinking_sphinx/errors.rb +7 -0
  25. data/lib/thinking_sphinx/excerpter.rb +1 -0
  26. data/lib/thinking_sphinx/facet_search.rb +42 -19
  27. data/lib/thinking_sphinx/masks/scopes_mask.rb +7 -0
  28. data/lib/thinking_sphinx/middlewares.rb +27 -33
  29. data/lib/thinking_sphinx/middlewares/active_record_translator.rb +18 -18
  30. data/lib/thinking_sphinx/middlewares/geographer.rb +49 -16
  31. data/lib/thinking_sphinx/middlewares/sphinxql.rb +128 -58
  32. data/lib/thinking_sphinx/panes/excerpts_pane.rb +7 -3
  33. data/lib/thinking_sphinx/rake_interface.rb +10 -0
  34. data/lib/thinking_sphinx/real_time.rb +7 -1
  35. data/lib/thinking_sphinx/real_time/attribute.rb +4 -0
  36. data/lib/thinking_sphinx/real_time/callbacks/real_time_callbacks.rb +14 -10
  37. data/lib/thinking_sphinx/real_time/index.rb +20 -12
  38. data/lib/thinking_sphinx/search/glaze.rb +5 -0
  39. data/lib/thinking_sphinx/search/query.rb +4 -8
  40. data/lib/thinking_sphinx/tasks.rb +8 -0
  41. data/spec/acceptance/excerpts_spec.rb +22 -0
  42. data/spec/acceptance/remove_deleted_records_spec.rb +10 -0
  43. data/spec/acceptance/searching_across_models_spec.rb +10 -0
  44. data/spec/acceptance/searching_with_filters_spec.rb +15 -0
  45. data/spec/acceptance/specifying_sql_spec.rb +3 -3
  46. data/spec/acceptance/sphinx_scopes_spec.rb +11 -0
  47. data/spec/internal/app/indices/product_index.rb +2 -0
  48. data/spec/internal/app/models/categorisation.rb +6 -0
  49. data/spec/internal/app/models/category.rb +3 -0
  50. data/spec/internal/app/models/product.rb +4 -1
  51. data/spec/internal/db/schema.rb +10 -0
  52. data/spec/thinking_sphinx/active_record/base_spec.rb +33 -0
  53. data/spec/thinking_sphinx/active_record/callbacks/delete_callbacks_spec.rb +4 -35
  54. data/spec/thinking_sphinx/active_record/sql_builder_spec.rb +5 -10
  55. data/spec/thinking_sphinx/active_record/sql_source_spec.rb +4 -3
  56. data/spec/thinking_sphinx/configuration_spec.rb +26 -6
  57. data/spec/thinking_sphinx/connection_spec.rb +4 -1
  58. data/spec/thinking_sphinx/deletion_spec.rb +76 -0
  59. data/spec/thinking_sphinx/facet_search_spec.rb +54 -5
  60. data/spec/thinking_sphinx/panes/excerpts_pane_spec.rb +4 -6
  61. data/spec/thinking_sphinx/rake_interface_spec.rb +35 -0
  62. data/spec/thinking_sphinx/real_time/callbacks/real_time_callbacks_spec.rb +68 -28
  63. data/spec/thinking_sphinx/search/glaze_spec.rb +19 -0
  64. data/spec/thinking_sphinx/search/query_spec.rb +39 -2
  65. data/thinking-sphinx.gemspec +2 -2
  66. metadata +31 -45
@@ -1,164 +1,143 @@
1
- class ThinkingSphinx::ActiveRecord::SQLBuilder
2
- attr_reader :source
1
+ module ThinkingSphinx
2
+ module ActiveRecord
3
+ class SQLBuilder
4
+ attr_reader :source
3
5
 
4
- def initialize(source)
5
- @source = source
6
- end
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
- minimum = source.adapter.convert_nulls "MIN(#{quoted_primary_key})", 1
24
- maximum = source.adapter.convert_nulls "MAX(#{quoted_primary_key})", 1
10
+ def sql_query
11
+ statement.to_relation.to_sql.gsub(/\n/, "\\\n")
12
+ end
25
13
 
26
- relation = source.model.unscoped
27
- relation = relation.select "#{minimum}, #{maximum}"
28
- relation = relation.where where_clause(true)
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
- relation.to_sql
31
- end
19
+ def sql_query_info
20
+ statement.to_query_info_relation.to_sql
21
+ end
32
22
 
33
- def sql_query_info
34
- relation = source.model.unscoped
35
- relation.where("#{quoted_primary_key} = #{reversed_document_id}").to_sql
36
- end
23
+ def sql_query_pre
24
+ query.to_query
25
+ end
37
26
 
38
- def sql_query_pre
39
- queries = []
27
+ private
40
28
 
41
- reset_delta = delta_processor && !source.delta?
42
- max_len = source.options[:group_concat_max_len]
29
+ def query
30
+ Query.new(self)
31
+ end
43
32
 
44
- queries << delta_processor.reset_query if reset_delta
45
- queries << "SET SESSION group_concat_max_len = #{max_len}" if max_len
46
- queries += source.adapter.utf8_query_pre if source.options[:utf8?]
33
+ def statement
34
+ Statement.new(self)
35
+ end
47
36
 
48
- queries.compact
49
- end
37
+ def config
38
+ ThinkingSphinx::Configuration.instance
39
+ end
50
40
 
51
- private
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
- def config
54
- ThinkingSphinx::Configuration.instance
55
- end
47
+ def base_join
48
+ @base_join ||= join_dependency_class.new model, [], initial_joins
49
+ end
56
50
 
57
- def model
58
- source.model
59
- end
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
- def base_join
62
- @base_join ||= join_dependency_class.new model, [], initial_joins
63
- end
59
+ def custom_joins
60
+ @custom_joins ||= source.associations.select(&:string?).collect(&:to_s)
61
+ end
64
62
 
65
- def delta_processor
66
- source.delta_processor
67
- end
63
+ def quote_column(column)
64
+ model.connection.quote_column_name(column)
65
+ end
68
66
 
69
- def associations
70
- @associations ||= ThinkingSphinx::ActiveRecord::Associations.new(model).tap do |assocs|
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
- def custom_joins
78
- @custom_joins ||= source.associations.select &:string?
79
- end
71
+ def quoted_inheritance_column
72
+ "#{model.quoted_table_name}.#{quote_column(model.inheritance_column)}"
73
+ end
80
74
 
81
- def quote_column(column)
82
- model.connection.quote_column_name(column)
83
- end
75
+ def pre_select
76
+ ('SQL_NO_CACHE ' if source.type == 'mysql').to_s
77
+ end
84
78
 
85
- def quoted_primary_key
86
- "#{model.quoted_table_name}.#{quote_column(source.primary_key)}"
87
- end
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
- def quoted_inheritance_column
90
- "#{model.quoted_table_name}.#{quote_column model.inheritance_column}"
91
- end
84
+ def reversed_document_id
85
+ "($id - #{source.offset}) / #{config.indices.count}"
86
+ end
92
87
 
93
- def pre_select
94
- source.type == 'mysql' ? 'SQL_NO_CACHE ' : ''
95
- end
88
+ def attribute_presenters
89
+ @attribute_presenters ||= property_sql_presenters_for(source.attributes)
90
+ end
96
91
 
97
- def document_id
98
- quoted_alias = quote_column source.primary_key
99
- "#{quoted_primary_key} * #{config.indices.count} + #{source.offset} AS #{quoted_alias}"
100
- end
92
+ def field_presenters
93
+ @field_presenters ||= property_sql_presenters_for(source.fields)
94
+ end
101
95
 
102
- def reversed_document_id
103
- "($id - #{source.offset}) / #{config.indices.count}"
104
- end
96
+ def property_sql_presenters_for(fields)
97
+ fields.collect { |field| property_sql_presenter_for(field) }
98
+ end
105
99
 
106
- def attribute_presenters
107
- @attribute_presenters ||= begin
108
- source.attributes.collect { |attribute|
100
+ def property_sql_presenter_for(field)
109
101
  ThinkingSphinx::ActiveRecord::PropertySQLPresenter.new(
110
- attribute, source.adapter, associations
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
- def where_clause(for_range = false)
133
- logic = []
106
+ def inheritance_column_condition
107
+ "#{quoted_inheritance_column} = '#{model_name}'"
108
+ end
134
109
 
135
- unless for_range || source.disable_range?
136
- logic << "#{quoted_primary_key} >= $start"
137
- logic << "#{quoted_primary_key} <= $end"
138
- end
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
- unless model.descends_from_active_record?
141
- klass = model.store_full_sti_class ? model.name : model.name.demodulize
142
- logic << "#{quoted_inheritance_column} = '#{klass}'"
143
- end
117
+ def presenters_to_group(presenters)
118
+ presenters.collect(&:to_group)
119
+ end
144
120
 
145
- logic << delta_processor.clause(source.delta?) if delta_processor
146
- logic += source.conditions unless for_range
121
+ def presenters_to_select(presenters)
122
+ presenters.collect(&:to_select)
123
+ end
147
124
 
148
- logic.compact.join(' AND ')
149
- end
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
- def group_clause
152
- internal_groupings = []
153
- if model.column_names.include?(model.inheritance_column)
154
- internal_groupings << quoted_inheritance_column
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