thinking-sphinx 1.5.0 → 2.0.0.rc1

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 (104) hide show
  1. data/README.textile +15 -48
  2. data/VERSION +1 -0
  3. data/features/attribute_transformation.feature +7 -7
  4. data/features/attribute_updates.feature +16 -18
  5. data/features/deleting_instances.feature +13 -16
  6. data/features/excerpts.feature +0 -8
  7. data/features/facets.feature +19 -25
  8. data/features/handling_edits.feature +20 -25
  9. data/features/searching_across_models.feature +1 -1
  10. data/features/searching_by_index.feature +5 -6
  11. data/features/searching_by_model.feature +29 -29
  12. data/features/sphinx_scopes.feature +0 -26
  13. data/features/step_definitions/common_steps.rb +6 -18
  14. data/features/step_definitions/scope_steps.rb +0 -4
  15. data/features/step_definitions/search_steps.rb +4 -9
  16. data/features/support/env.rb +10 -3
  17. data/features/thinking_sphinx/db/fixtures/alphas.rb +10 -8
  18. data/features/thinking_sphinx/db/fixtures/cats.rb +1 -1
  19. data/features/thinking_sphinx/db/fixtures/dogs.rb +1 -1
  20. data/features/thinking_sphinx/db/fixtures/foxes.rb +1 -1
  21. data/features/thinking_sphinx/db/fixtures/people.rb +1 -1
  22. data/features/thinking_sphinx/db/fixtures/posts.rb +1 -5
  23. data/features/thinking_sphinx/db/migrations/create_posts.rb +0 -1
  24. data/features/thinking_sphinx/models/alpha.rb +0 -1
  25. data/features/thinking_sphinx/models/beta.rb +0 -5
  26. data/features/thinking_sphinx/models/developer.rb +1 -6
  27. data/features/thinking_sphinx/models/music.rb +1 -3
  28. data/features/thinking_sphinx/models/person.rb +1 -2
  29. data/features/thinking_sphinx/models/post.rb +0 -1
  30. data/lib/cucumber/thinking_sphinx/external_world.rb +4 -8
  31. data/lib/cucumber/thinking_sphinx/internal_world.rb +27 -36
  32. data/lib/thinking_sphinx.rb +60 -132
  33. data/lib/thinking_sphinx/active_record.rb +98 -124
  34. data/lib/thinking_sphinx/active_record/attribute_updates.rb +13 -17
  35. data/lib/thinking_sphinx/active_record/delta.rb +15 -21
  36. data/lib/thinking_sphinx/active_record/has_many_association.rb +23 -16
  37. data/lib/thinking_sphinx/active_record/scopes.rb +0 -18
  38. data/lib/thinking_sphinx/adapters/abstract_adapter.rb +15 -63
  39. data/lib/thinking_sphinx/adapters/mysql_adapter.rb +0 -4
  40. data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +24 -65
  41. data/lib/thinking_sphinx/association.rb +11 -36
  42. data/lib/thinking_sphinx/attribute.rb +85 -92
  43. data/lib/thinking_sphinx/auto_version.rb +3 -21
  44. data/lib/thinking_sphinx/class_facet.rb +3 -8
  45. data/lib/thinking_sphinx/configuration.rb +58 -114
  46. data/lib/thinking_sphinx/context.rb +20 -22
  47. data/lib/thinking_sphinx/core/array.rb +13 -0
  48. data/lib/thinking_sphinx/deltas.rb +0 -2
  49. data/lib/thinking_sphinx/deltas/default_delta.rb +22 -18
  50. data/lib/thinking_sphinx/deploy/capistrano.rb +31 -30
  51. data/lib/thinking_sphinx/excerpter.rb +1 -2
  52. data/lib/thinking_sphinx/facet.rb +35 -45
  53. data/lib/thinking_sphinx/facet_search.rb +24 -58
  54. data/lib/thinking_sphinx/field.rb +0 -18
  55. data/lib/thinking_sphinx/index.rb +36 -38
  56. data/lib/thinking_sphinx/index/builder.rb +59 -74
  57. data/lib/thinking_sphinx/property.rb +45 -66
  58. data/lib/thinking_sphinx/railtie.rb +35 -0
  59. data/lib/thinking_sphinx/search.rb +250 -506
  60. data/lib/thinking_sphinx/source.rb +31 -50
  61. data/lib/thinking_sphinx/source/internal_properties.rb +3 -8
  62. data/lib/thinking_sphinx/source/sql.rb +31 -71
  63. data/lib/thinking_sphinx/tasks.rb +27 -48
  64. data/spec/thinking_sphinx/active_record/delta_spec.rb +41 -36
  65. data/spec/thinking_sphinx/active_record/has_many_association_spec.rb +0 -96
  66. data/spec/thinking_sphinx/active_record/scopes_spec.rb +29 -29
  67. data/spec/thinking_sphinx/active_record_spec.rb +169 -140
  68. data/spec/thinking_sphinx/association_spec.rb +2 -20
  69. data/spec/thinking_sphinx/attribute_spec.rb +97 -101
  70. data/spec/thinking_sphinx/auto_version_spec.rb +11 -75
  71. data/spec/thinking_sphinx/configuration_spec.rb +62 -63
  72. data/spec/thinking_sphinx/context_spec.rb +66 -66
  73. data/spec/thinking_sphinx/facet_search_spec.rb +99 -99
  74. data/spec/thinking_sphinx/facet_spec.rb +4 -30
  75. data/spec/thinking_sphinx/field_spec.rb +3 -17
  76. data/spec/thinking_sphinx/index/builder_spec.rb +132 -169
  77. data/spec/thinking_sphinx/index_spec.rb +39 -45
  78. data/spec/thinking_sphinx/search_methods_spec.rb +33 -37
  79. data/spec/thinking_sphinx/search_spec.rb +269 -491
  80. data/spec/thinking_sphinx/source_spec.rb +48 -62
  81. data/spec/thinking_sphinx_spec.rb +49 -49
  82. data/tasks/distribution.rb +46 -0
  83. data/tasks/testing.rb +74 -0
  84. metadata +123 -199
  85. data/features/field_sorting.feature +0 -18
  86. data/features/thinking_sphinx/db/.gitignore +0 -1
  87. data/features/thinking_sphinx/db/fixtures/post_keywords.txt +0 -1
  88. data/features/thinking_sphinx/models/andrew.rb +0 -17
  89. data/lib/thinking-sphinx.rb +0 -1
  90. data/lib/thinking_sphinx/active_record/has_many_association_with_scopes.rb +0 -21
  91. data/lib/thinking_sphinx/bundled_search.rb +0 -40
  92. data/lib/thinking_sphinx/connection.rb +0 -71
  93. data/lib/thinking_sphinx/deltas/delete_job.rb +0 -16
  94. data/lib/thinking_sphinx/deltas/index_job.rb +0 -17
  95. data/lib/thinking_sphinx/rails_additions.rb +0 -181
  96. data/spec/fixtures/data.sql +0 -32
  97. data/spec/fixtures/database.yml.default +0 -3
  98. data/spec/fixtures/models.rb +0 -161
  99. data/spec/fixtures/structure.sql +0 -146
  100. data/spec/spec_helper.rb +0 -54
  101. data/spec/sphinx_helper.rb +0 -67
  102. data/spec/thinking_sphinx/adapters/abstract_adapter_spec.rb +0 -163
  103. data/spec/thinking_sphinx/connection_spec.rb +0 -77
  104. data/spec/thinking_sphinx/rails_additions_spec.rb +0 -203
@@ -3,7 +3,7 @@ module ThinkingSphinx
3
3
  # This module contains all the delta-related code for models. There isn't
4
4
  # really anything you need to call manually in here - except perhaps
5
5
  # index_delta, but not sure what reason why.
6
- #
6
+ #
7
7
  module Delta
8
8
  # Code for after_commit callback is written by Eli Miller:
9
9
  # http://elimiller.blogspot.com/2007/06/proper-cache-expiry-with-aftercommit.html
@@ -16,42 +16,36 @@ module ThinkingSphinx
16
16
  # if running in the test environment.
17
17
  #
18
18
  def index_delta(instance = nil)
19
- delta_objects.each { |obj| obj.index(self, instance) }
19
+ delta_object.index(self, instance)
20
20
  end
21
-
22
- def delta_objects
23
- self.sphinx_indexes.collect(&:delta_object).compact
21
+
22
+ def delta_object
23
+ self.sphinx_indexes.first.delta_object
24
24
  end
25
25
  end
26
-
26
+
27
27
  def toggled_delta?
28
- self.class.delta_objects.any? { |obj| obj.toggled(self) }
28
+ self.class.delta_object.toggled(self)
29
29
  end
30
-
30
+
31
31
  private
32
-
32
+
33
33
  # Set the delta value for the model to be true.
34
34
  def toggle_delta
35
- self.class.delta_objects.each { |obj|
36
- obj.toggle(self)
37
- } if should_toggle_delta?
35
+ self.class.delta_object.toggle(self) if should_toggle_delta?
38
36
  end
39
-
37
+
40
38
  # Build the delta index for the related model. This won't be called
41
39
  # if running in the test environment.
42
- #
40
+ #
43
41
  def index_delta
44
- self.class.index_delta(self) if self.class.delta_objects.any? { |obj|
45
- obj.toggled(self)
46
- }
42
+ self.class.index_delta(self) if self.class.delta_object.toggled(self)
47
43
  end
48
-
44
+
49
45
  def should_toggle_delta?
50
- return fire_delta? if respond_to?(:fire_delta?)
51
-
52
46
  self.new_record? || indexed_data_changed?
53
47
  end
54
-
48
+
55
49
  def indexed_data_changed?
56
50
  sphinx_indexes.any? { |index|
57
51
  index.fields.any? { |field| field.changed?(self) } ||
@@ -2,23 +2,26 @@ module ThinkingSphinx
2
2
  module ActiveRecord
3
3
  module HasManyAssociation
4
4
  def search(*args)
5
- @reflection.klass.search(*association_args(args))
6
- end
7
-
8
- def facets(*args)
9
- @reflection.klass.facets(*association_args(args))
10
- end
11
-
12
- private
13
-
14
- def association_args(args)
15
- options = args.extract_options!
5
+ options = args.extract_options!
16
6
  options[:with] ||= {}
17
7
  options[:with].merge! default_filter
18
-
19
- args + [options]
8
+
9
+ args << options
10
+ @reflection.klass.search(*args)
11
+ end
12
+
13
+ def method_missing(method, *args, &block)
14
+ if responds_to_scope(method)
15
+ @reflection.klass.
16
+ search(:with => default_filter).
17
+ send(method, *args, &block)
18
+ else
19
+ super
20
+ end
20
21
  end
21
-
22
+
23
+ private
24
+
22
25
  def attribute_for_foreign_key
23
26
  foreign_key = @reflection.primary_key_name
24
27
  stack = [@reflection.options[:through]].compact
@@ -27,8 +30,7 @@ module ThinkingSphinx
27
30
  (@reflection.klass.sphinx_indexes || []).each do |index|
28
31
  attribute = index.attributes.detect { |attrib|
29
32
  attrib.columns.length == 1 &&
30
- attrib.columns.first.__name == foreign_key.to_sym ||
31
- attrib.alias == foreign_key.to_sym
33
+ attrib.columns.first.__name == foreign_key.to_sym
32
34
  }
33
35
  return attribute unless attribute.nil?
34
36
  end
@@ -39,6 +41,11 @@ module ThinkingSphinx
39
41
  def default_filter
40
42
  {attribute_for_foreign_key.unique_name => @owner.id}
41
43
  end
44
+
45
+ def responds_to_scope(scope)
46
+ @reflection.klass.respond_to?(:sphinx_scopes) &&
47
+ @reflection.klass.sphinx_scopes.include?(scope)
48
+ end
42
49
  end
43
50
  end
44
51
  end
@@ -17,7 +17,6 @@ module ThinkingSphinx
17
17
  # The scope is automatically applied when the search method is called. It
18
18
  # will only be applied if it is an existing sphinx_scope.
19
19
  def default_sphinx_scope(sphinx_scope_name)
20
- add_sphinx_scopes_support_to_has_many_associations
21
20
  @default_sphinx_scope = sphinx_scope_name
22
21
  end
23
22
 
@@ -44,8 +43,6 @@ module ThinkingSphinx
44
43
  # @articles = Article.latest_first.search 'pancakes'
45
44
  #
46
45
  def sphinx_scope(method, &block)
47
- add_sphinx_scopes_support_to_has_many_associations
48
-
49
46
  @sphinx_scopes ||= []
50
47
  @sphinx_scopes << method
51
48
 
@@ -56,13 +53,6 @@ module ThinkingSphinx
56
53
 
57
54
  ThinkingSphinx::Search.new(options)
58
55
  end
59
-
60
- define_method("#{method}_without_default".to_sym) do |*args|
61
- options = {:classes => classes_option, :ignore_default => true}
62
- options.merge! block.call(*args)
63
-
64
- ThinkingSphinx::Search.new(options)
65
- end
66
56
  end
67
57
  end
68
58
 
@@ -79,14 +69,6 @@ module ThinkingSphinx
79
69
 
80
70
  sphinx_scopes.clear
81
71
  end
82
-
83
- def add_sphinx_scopes_support_to_has_many_associations
84
- scope_mixin = ::ThinkingSphinx::ActiveRecord::HasManyAssociationWithScopes
85
-
86
- ::ActiveRecord::Associations::HasManyAssociation.send(:include, scope_mixin)
87
- ::ActiveRecord::Associations::HasManyThroughAssociation.send(:include, scope_mixin)
88
- end
89
-
90
72
  end
91
73
  end
92
74
  end
@@ -3,90 +3,42 @@ module ThinkingSphinx
3
3
  def initialize(model)
4
4
  @model = model
5
5
  end
6
-
6
+
7
7
  def setup
8
8
  # Deliberately blank - subclasses should do something though. Well, if
9
9
  # they need to.
10
10
  end
11
-
11
+
12
12
  def self.detect(model)
13
- adapter = adapter_for_model model
14
- case adapter
15
- when :mysql
16
- ThinkingSphinx::MysqlAdapter.new model
17
- when :postgresql
18
- ThinkingSphinx::PostgreSQLAdapter.new model
19
- when Class
20
- adapter.new model
21
- else
22
- raise "Invalid Database Adapter: Sphinx only supports MySQL and PostgreSQL, not #{adapter}"
23
- end
24
- end
25
-
26
- def self.adapter_for_model(model)
27
- case ThinkingSphinx.database_adapter
28
- when String
29
- ThinkingSphinx.database_adapter.to_sym
30
- when NilClass
31
- standard_adapter_for_model model
32
- when Proc
33
- ThinkingSphinx.database_adapter.call model
34
- else
35
- ThinkingSphinx.database_adapter
36
- end
37
- end
38
-
39
- def self.standard_adapter_for_model(model)
40
13
  case model.connection.class.name
41
14
  when "ActiveRecord::ConnectionAdapters::MysqlAdapter",
42
- "ActiveRecord::ConnectionAdapters::MysqlplusAdapter",
43
- "ActiveRecord::ConnectionAdapters::Mysql2Adapter",
44
- "ActiveRecord::ConnectionAdapters::NullDBAdapter"
45
- :mysql
15
+ "ActiveRecord::ConnectionAdapters::MysqlplusAdapter"
16
+ ThinkingSphinx::MysqlAdapter.new model
46
17
  when "ActiveRecord::ConnectionAdapters::PostgreSQLAdapter"
47
- :postgresql
18
+ ThinkingSphinx::PostgreSQLAdapter.new model
48
19
  when "ActiveRecord::ConnectionAdapters::JdbcAdapter"
49
- case model.connection.config[:adapter]
50
- when "jdbcmysql"
51
- :mysql
52
- when "jdbcpostgresql"
53
- :postgresql
54
- when "jdbc"
55
- match = /^jdbc:(mysql|postgresql):\/\//.match(model.connection.config[:url])
56
- if match
57
- match[1].to_sym
58
- else
59
- model.connection.config[:adapter]
60
- end
20
+ if model.connection.config[:adapter] == "jdbcmysql"
21
+ ThinkingSphinx::MysqlAdapter.new model
22
+ elsif model.connection.config[:adapter] == "jdbcpostgresql"
23
+ ThinkingSphinx::PostgreSQLAdapter.new model
61
24
  else
62
- model.connection.config[:adapter].to_sym
25
+ raise "Invalid Database Adapter: Sphinx only supports MySQL and PostgreSQL"
63
26
  end
64
27
  else
65
- model.connection.class.name
28
+ raise "Invalid Database Adapter: Sphinx only supports MySQL and PostgreSQL, not #{model.connection.class.name}"
66
29
  end
67
30
  end
68
-
31
+
69
32
  def quote_with_table(column)
70
33
  "#{@model.quoted_table_name}.#{@model.connection.quote_column_name(column)}"
71
34
  end
72
-
35
+
73
36
  def bigint_pattern
74
37
  /bigint/i
75
38
  end
76
-
77
- def downcase(clause)
78
- "LOWER(#{clause})"
79
- end
80
-
81
- def case(expression, pairs, default)
82
- "CASE #{expression} " +
83
- pairs.keys.inject('') { |string, key|
84
- string + "WHEN '#{key}' THEN #{pairs[key]} "
85
- } + "ELSE #{default} END"
86
- end
87
-
39
+
88
40
  protected
89
-
41
+
90
42
  def connection
91
43
  @connection ||= @model.connection
92
44
  end
@@ -28,10 +28,6 @@ module ThinkingSphinx
28
28
  "CAST(#{clause} AS UNSIGNED)"
29
29
  end
30
30
 
31
- def cast_to_int(clause)
32
- "CAST(#{clause} AS SIGNED)"
33
- end
34
-
35
31
  def convert_nulls(clause, default = '')
36
32
  default = "'#{default}'" if default.is_a?(String)
37
33
 
@@ -4,49 +4,37 @@ module ThinkingSphinx
4
4
  create_array_accum_function
5
5
  create_crc32_function
6
6
  end
7
-
7
+
8
8
  def sphinx_identifier
9
9
  "pgsql"
10
10
  end
11
-
11
+
12
12
  def concatenate(clause, separator = ' ')
13
13
  if clause[/^COALESCE/]
14
14
  clause.split('), ').join(") || '#{separator}' || ")
15
15
  else
16
16
  clause.split(', ').collect { |field|
17
- "CAST(COALESCE(#{field}::varchar, '') as varchar)"
17
+ "CAST(COALESCE(#{field}, '') as varchar)"
18
18
  }.join(" || '#{separator}' || ")
19
19
  end
20
20
  end
21
-
21
+
22
22
  def group_concatenate(clause, separator = ' ')
23
- if server_version >= 80400
24
- "array_to_string(array_agg(COALESCE(#{clause}, '0')), '#{separator}')"
25
- else
26
- "array_to_string(array_accum(COALESCE(#{clause}, '0')), '#{separator}')"
27
- end
23
+ "array_to_string(array_accum(COALESCE(#{clause}, '0')), '#{separator}')"
28
24
  end
29
-
25
+
30
26
  def cast_to_string(clause)
31
27
  clause
32
28
  end
33
-
29
+
34
30
  def cast_to_datetime(clause)
35
- if ThinkingSphinx::Configuration.instance.use_64_bit
36
- "cast(floor(extract(epoch from #{clause})) as bigint)"
37
- else
38
- "cast(floor(extract(epoch from #{clause})) as int)"
39
- end
31
+ "cast(extract(epoch from #{clause}) as int)"
40
32
  end
41
-
33
+
42
34
  def cast_to_unsigned(clause)
43
35
  clause
44
36
  end
45
-
46
- def cast_to_int(clause)
47
- "#{clause}::INT8"
48
- end
49
-
37
+
50
38
  def convert_nulls(clause, default = '')
51
39
  default = case default
52
40
  when String
@@ -58,44 +46,34 @@ module ThinkingSphinx
58
46
  else
59
47
  default
60
48
  end
61
-
49
+
62
50
  "COALESCE(#{clause}, #{default})"
63
51
  end
64
-
52
+
65
53
  def boolean(value)
66
54
  value ? 'TRUE' : 'FALSE'
67
55
  end
68
-
56
+
69
57
  def crc(clause, blank_to_null = false)
70
58
  clause = "NULLIF(#{clause},'')" if blank_to_null
71
59
  "crc32(#{clause})"
72
60
  end
73
-
61
+
74
62
  def utf8_query_pre
75
63
  nil
76
64
  end
77
-
65
+
78
66
  def time_difference(diff)
79
67
  "current_timestamp - interval '#{diff} seconds'"
80
68
  end
81
-
69
+
82
70
  def utc_query_pre
83
- "SET TIME ZONE 'UTC'"
71
+ 'SET TIME ZONE UTC'
84
72
  end
85
-
73
+
86
74
  private
87
-
75
+
88
76
  def execute(command, output_error = false)
89
- if RUBY_PLATFORM == 'java'
90
- connection.transaction do
91
- execute_command command, output_error
92
- end
93
- else
94
- execute_command command, output_error
95
- end
96
- end
97
-
98
- def execute_command(command, output_error = false)
99
77
  connection.execute "begin"
100
78
  connection.execute "savepoint ts"
101
79
  begin
@@ -107,11 +85,9 @@ module ThinkingSphinx
107
85
  connection.execute "release savepoint ts"
108
86
  connection.execute "commit"
109
87
  end
110
-
88
+
111
89
  def create_array_accum_function
112
- if server_version >= 80311
113
- return
114
- elsif server_version > 80200
90
+ if connection.raw_connection.respond_to?(:server_version) && connection.raw_connection.server_version > 80200
115
91
  execute <<-SQL
116
92
  CREATE AGGREGATE array_accum (anyelement)
117
93
  (
@@ -132,7 +108,7 @@ module ThinkingSphinx
132
108
  SQL
133
109
  end
134
110
  end
135
-
111
+
136
112
  def create_crc32_function
137
113
  execute "CREATE LANGUAGE 'plpgsql';"
138
114
  function = <<-SQL
@@ -141,16 +117,10 @@ module ThinkingSphinx
141
117
  DECLARE tmp bigint;
142
118
  DECLARE i int;
143
119
  DECLARE j int;
144
- DECLARE byte_length int;
145
120
  DECLARE word_array bytea;
146
121
  BEGIN
147
- IF COALESCE(word, '') = '' THEN
148
- return 0;
149
- END IF;
150
-
151
122
  i = 0;
152
123
  tmp = 4294967295;
153
- byte_length = bit_length(word) / 8;
154
124
  word_array = decode(replace(word, E'\\\\', E'\\\\\\\\'), 'escape');
155
125
  LOOP
156
126
  tmp = (tmp # get_byte(word_array, i))::bigint;
@@ -163,26 +133,15 @@ module ThinkingSphinx
163
133
  EXIT;
164
134
  END IF;
165
135
  END LOOP;
166
- IF i >= byte_length THEN
136
+ IF i >= char_length(word) THEN
167
137
  EXIT;
168
138
  END IF;
169
139
  END LOOP;
170
140
  return (tmp # 4294967295);
171
141
  END
172
- $$ IMMUTABLE LANGUAGE plpgsql;
142
+ $$ IMMUTABLE STRICT LANGUAGE plpgsql;
173
143
  SQL
174
144
  execute function, true
175
145
  end
176
-
177
- def server_version
178
- if RUBY_PLATFORM == 'java'
179
- (connection.raw_connection.connection.server_major_version * 10000) +
180
- (connection.raw_connection.connection.server_minor_version * 100)
181
- elsif connection.raw_connection.respond_to?(:server_version)
182
- connection.raw_connection.server_version
183
- else
184
- 0
185
- end
186
- end
187
146
  end
188
147
  end