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.
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