freelancing-god-thinking-sphinx 0.9.8 → 0.9.10

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 (40) hide show
  1. data/README +20 -1
  2. data/lib/thinking_sphinx.rb +30 -2
  3. data/lib/thinking_sphinx/active_record.rb +25 -11
  4. data/lib/thinking_sphinx/active_record/delta.rb +46 -53
  5. data/lib/thinking_sphinx/active_record/has_many_association.rb +1 -1
  6. data/lib/thinking_sphinx/active_record/search.rb +8 -1
  7. data/lib/thinking_sphinx/adapters/abstract_adapter.rb +27 -0
  8. data/lib/thinking_sphinx/adapters/mysql_adapter.rb +9 -0
  9. data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +84 -0
  10. data/lib/thinking_sphinx/association.rb +4 -0
  11. data/lib/thinking_sphinx/attribute.rb +4 -2
  12. data/lib/thinking_sphinx/collection.rb +105 -0
  13. data/lib/thinking_sphinx/configuration.rb +112 -75
  14. data/lib/thinking_sphinx/field.rb +11 -3
  15. data/lib/thinking_sphinx/index.rb +119 -26
  16. data/lib/thinking_sphinx/index/builder.rb +30 -22
  17. data/lib/thinking_sphinx/index/faux_column.rb +13 -0
  18. data/lib/thinking_sphinx/rails_additions.rb +13 -1
  19. data/lib/thinking_sphinx/search.rb +40 -81
  20. data/spec/unit/thinking_sphinx/active_record/delta_spec.rb +73 -127
  21. data/spec/unit/thinking_sphinx/active_record/has_many_association_spec.rb +2 -2
  22. data/spec/unit/thinking_sphinx/active_record/search_spec.rb +26 -0
  23. data/spec/unit/thinking_sphinx/active_record_spec.rb +94 -22
  24. data/spec/unit/thinking_sphinx/attribute_spec.rb +8 -4
  25. data/spec/unit/thinking_sphinx/collection_spec.rb +71 -0
  26. data/spec/unit/thinking_sphinx/configuration_spec.rb +149 -113
  27. data/spec/unit/thinking_sphinx/field_spec.rb +13 -4
  28. data/spec/unit/thinking_sphinx/index/builder_spec.rb +1 -0
  29. data/spec/unit/thinking_sphinx/index/faux_column_spec.rb +27 -0
  30. data/spec/unit/thinking_sphinx/index_spec.rb +79 -29
  31. data/spec/unit/thinking_sphinx/search_spec.rb +114 -74
  32. data/spec/unit/thinking_sphinx_spec.rb +21 -0
  33. data/tasks/thinking_sphinx_tasks.rb +24 -10
  34. metadata +21 -8
  35. data/lib/riddle.rb +0 -26
  36. data/lib/riddle/client.rb +0 -639
  37. data/lib/riddle/client/filter.rb +0 -44
  38. data/lib/riddle/client/message.rb +0 -65
  39. data/lib/riddle/client/response.rb +0 -84
  40. data/lib/test.rb +0 -46
data/README CHANGED
@@ -54,4 +54,23 @@ Since I first released this library, there's been quite a few people who have su
54
54
  - Andrew Bennett
55
55
  - Jordan Fowler
56
56
  - Seth Walker
57
- - Joe Noon
57
+ - Joe Noon
58
+ - Wolfgang Postler
59
+ - Rick Olson
60
+ - Killian Murphy
61
+ - Morten Primdahl
62
+ - Ryan Bates
63
+ - David Eisinger
64
+ - Shay Arnett
65
+ - Minh Tran
66
+ - Jeremy Durham
67
+ - Piotr Sarnacki
68
+ - Matt Johnson
69
+ - Nicolas Blanco
70
+ - Max Lapshin
71
+ - Josh Natanson
72
+ - Philip Hallstrom
73
+ - Christian Rishøj
74
+ - Mike Flester
75
+ - Jim Remsik
76
+ - Kennon Ballou
@@ -1,15 +1,25 @@
1
+ Dir[File.join(File.dirname(__FILE__), '../vendor/*/lib')].each do |path|
2
+ $LOAD_PATH.unshift path
3
+ end
4
+
1
5
  require 'active_record'
2
6
  require 'riddle'
7
+ require 'after_commit'
3
8
 
4
9
  require 'thinking_sphinx/active_record'
5
10
  require 'thinking_sphinx/association'
6
11
  require 'thinking_sphinx/attribute'
12
+ require 'thinking_sphinx/collection'
7
13
  require 'thinking_sphinx/configuration'
8
14
  require 'thinking_sphinx/field'
9
15
  require 'thinking_sphinx/index'
10
16
  require 'thinking_sphinx/rails_additions'
11
17
  require 'thinking_sphinx/search'
12
18
 
19
+ require 'thinking_sphinx/adapters/abstract_adapter'
20
+ require 'thinking_sphinx/adapters/mysql_adapter'
21
+ require 'thinking_sphinx/adapters/postgresql_adapter'
22
+
13
23
  ActiveRecord::Base.send(:include, ThinkingSphinx::ActiveRecord)
14
24
 
15
25
  Merb::Plugins.add_rakefiles(
@@ -20,7 +30,7 @@ module ThinkingSphinx
20
30
  module Version #:nodoc:
21
31
  Major = 0
22
32
  Minor = 9
23
- Tiny = 8
33
+ Tiny = 10
24
34
 
25
35
  String = [Major, Minor, Tiny].join('.')
26
36
  end
@@ -58,7 +68,7 @@ module ThinkingSphinx
58
68
  #
59
69
  def self.deltas_enabled?
60
70
  @@deltas_enabled = (ThinkingSphinx::Configuration.environment != 'test') if @@deltas_enabled.nil?
61
- @@deltas_enabled == true
71
+ @@deltas_enabled
62
72
  end
63
73
 
64
74
  # Enable/disable all delta indexing.
@@ -69,6 +79,24 @@ module ThinkingSphinx
69
79
  @@deltas_enabled = value
70
80
  end
71
81
 
82
+ @@updates_enabled = nil
83
+
84
+ # Check if updates are enabled. True by default, unless within the test
85
+ # environment.
86
+ #
87
+ def self.updates_enabled?
88
+ @@updates_enabled = (ThinkingSphinx::Configuration.environment != 'test') if @@updates_enabled.nil?
89
+ @@updates_enabled
90
+ end
91
+
92
+ # Enable/disable updates to Sphinx
93
+ #
94
+ # ThinkingSphinx.updates_enabled = false
95
+ #
96
+ def self.updates_enabled=(value)
97
+ @@updates_enabled = value
98
+ end
99
+
72
100
  # Checks to see if MySQL will allow simplistic GROUP BY statements. If not,
73
101
  # or if not using MySQL, this will return false.
74
102
  #
@@ -5,12 +5,12 @@ require 'thinking_sphinx/active_record/has_many_association'
5
5
  module ThinkingSphinx
6
6
  # Core additions to ActiveRecord models - define_index for creating indexes
7
7
  # for models. If you want to interrogate the index objects created for the
8
- # model, you can use the class-level accessor :indexes.
8
+ # model, you can use the class-level accessor :sphinx_indexes.
9
9
  #
10
10
  module ActiveRecord
11
11
  def self.included(base)
12
12
  base.class_eval do
13
- class_inheritable_array :indexes
13
+ class_inheritable_array :sphinx_indexes
14
14
  class << self
15
15
  # Allows creation of indexes for Sphinx. If you don't do this, there
16
16
  # isn't much point trying to search (or using this plugin at all,
@@ -64,10 +64,10 @@ module ThinkingSphinx
64
64
  def define_index(&block)
65
65
  return unless ThinkingSphinx.define_indexes?
66
66
 
67
- self.indexes ||= []
67
+ self.sphinx_indexes ||= []
68
68
  index = Index.new(self, &block)
69
69
 
70
- self.indexes << index
70
+ self.sphinx_indexes << index
71
71
  unless ThinkingSphinx.indexed_models.include?(self.name)
72
72
  ThinkingSphinx.indexed_models << self.name
73
73
  end
@@ -99,6 +99,10 @@ module ThinkingSphinx
99
99
  end
100
100
  result ^ 0xFFFFFFFF
101
101
  end
102
+
103
+ def to_crc32s
104
+ (subclasses << self).collect { |klass| klass.to_crc32 }
105
+ end
102
106
  end
103
107
  end
104
108
 
@@ -114,26 +118,36 @@ module ThinkingSphinx
114
118
  end
115
119
 
116
120
  def in_core_index?
117
- @in_core_index ||= self.class.search_for_id(self.id, "#{self.class.name.downcase}_core")
121
+ self.class.search_for_id(
122
+ self.sphinx_document_id,
123
+ "#{self.class.name.underscore.tr(':/\\', '_')}_core"
124
+ )
118
125
  end
119
126
 
120
127
  def toggle_deleted
121
- config = ThinkingSphinx::Configuration.new
128
+ return unless ThinkingSphinx.updates_enabled?
129
+
130
+ config = ThinkingSphinx::Configuration.instance
122
131
  client = Riddle::Client.new config.address, config.port
123
132
 
124
133
  client.update(
125
- "#{self.class.indexes.first.name}_core",
134
+ "#{self.class.sphinx_indexes.first.name}_core",
126
135
  ['sphinx_deleted'],
127
- {self.id => 1}
136
+ {self.sphinx_document_id => 1}
128
137
  ) if self.in_core_index?
129
138
 
130
139
  client.update(
131
- "#{self.class.indexes.first.name}_delta",
140
+ "#{self.class.sphinx_indexes.first.name}_delta",
132
141
  ['sphinx_deleted'],
133
- {self.id => 1}
142
+ {self.sphinx_document_id => 1}
134
143
  ) if ThinkingSphinx.deltas_enabled? &&
135
- self.class.indexes.any? { |index| index.delta? } &&
144
+ self.class.sphinx_indexes.any? { |index| index.delta? } &&
136
145
  self.delta?
137
146
  end
147
+
148
+ def sphinx_document_id
149
+ (self.id * ThinkingSphinx.indexed_models.size) +
150
+ ThinkingSphinx.indexed_models.index(self.class.name)
151
+ end
138
152
  end
139
153
  end
@@ -11,57 +11,55 @@ module ThinkingSphinx
11
11
  #
12
12
  def self.included(base)
13
13
  base.class_eval do
14
- # The define_callbacks method was added post Rails 2.0.2 - if it
15
- # doesn't exist, we define the callback manually
16
- #
17
- if respond_to?(:define_callbacks)
18
- define_callbacks :after_commit
19
- else
20
- class << self
21
- # Handle after_commit callbacks - call all the registered callbacks.
22
- #
23
- def after_commit(*callbacks, &block)
24
- callbacks << block if block_given?
25
- write_inheritable_array(:after_commit, callbacks)
14
+ class << self
15
+ # Temporarily disable delta indexing inside a block, then perform a single
16
+ # rebuild of index at the end.
17
+ #
18
+ # Useful when performing updates to batches of models to prevent
19
+ # the delta index being rebuilt after each individual update.
20
+ #
21
+ # In the following example, the delta index will only be rebuilt once,
22
+ # not 10 times.
23
+ #
24
+ # SomeModel.suspended_delta do
25
+ # 10.times do
26
+ # SomeModel.create( ... )
27
+ # end
28
+ # end
29
+ #
30
+ def suspended_delta(reindex_after = true, &block)
31
+ original_setting = ThinkingSphinx.deltas_enabled?
32
+ ThinkingSphinx.deltas_enabled = false
33
+ begin
34
+ yield
35
+ ensure
36
+ ThinkingSphinx.deltas_enabled = original_setting
37
+ self.index_delta if reindex_after
26
38
  end
27
39
  end
40
+
41
+ # Build the delta index for the related model. This won't be called
42
+ # if running in the test environment.
43
+ #
44
+ def index_delta(instance = nil)
45
+ return true unless ThinkingSphinx.updates_enabled? &&
46
+ ThinkingSphinx.deltas_enabled?
47
+
48
+ config = ThinkingSphinx::Configuration.instance
49
+ client = Riddle::Client.new config.address, config.port
50
+
51
+ client.update(
52
+ "#{self.sphinx_indexes.first.name}_core",
53
+ ['sphinx_deleted'],
54
+ {instance.sphinx_document_id => 1}
55
+ ) if instance && instance.in_core_index?
56
+
57
+ system "#{config.bin_path}indexer --config #{config.config_file} --rotate #{self.sphinx_indexes.first.name}_delta"
58
+
59
+ true
60
+ end
28
61
  end
29
62
 
30
- def after_commit
31
- # Deliberately blank.
32
- end
33
-
34
- # Normal boolean save wrapped in a handler for the after_commit
35
- # callback.
36
- #
37
- def save_with_after_commit_callback(*args)
38
- value = save_without_after_commit_callback(*args)
39
- callback(:after_commit) if value
40
- return value
41
- end
42
-
43
- alias_method_chain :save, :after_commit_callback
44
-
45
- # Forceful save wrapped in a handler for the after_commit callback.
46
- #
47
- def save_with_after_commit_callback!(*args)
48
- value = save_without_after_commit_callback!(*args)
49
- callback(:after_commit) if value
50
- return value
51
- end
52
-
53
- alias_method_chain :save!, :after_commit_callback
54
-
55
- # Normal destroy wrapped in a handler for the after_commit callback.
56
- #
57
- def destroy_with_after_commit_callback
58
- value = destroy_without_after_commit_callback
59
- callback(:after_commit) if value
60
- return value
61
- end
62
-
63
- alias_method_chain :destroy, :after_commit_callback
64
-
65
63
  private
66
64
 
67
65
  # Set the delta value for the model to be true.
@@ -73,12 +71,7 @@ module ThinkingSphinx
73
71
  # if running in the test environment.
74
72
  #
75
73
  def index_delta
76
- return true unless ThinkingSphinx.deltas_enabled?
77
-
78
- configuration = ThinkingSphinx::Configuration.new
79
- system "indexer --config #{configuration.config_file} --rotate #{self.class.indexes.first.name}_delta"
80
-
81
- true
74
+ self.class.index_delta(self)
82
75
  end
83
76
  end
84
77
  end
@@ -6,7 +6,7 @@ module ThinkingSphinx
6
6
  stack = [@reflection.options[:through]].compact
7
7
 
8
8
  attribute = nil
9
- (@reflection.klass.indexes || []).each do |index|
9
+ (@reflection.klass.sphinx_indexes || []).each do |index|
10
10
  attribute = index.attributes.detect { |attrib|
11
11
  attrib.columns.length == 1 &&
12
12
  attrib.columns.first.__name == foreign_key.to_sym &&
@@ -28,7 +28,14 @@ module ThinkingSphinx
28
28
  args << options
29
29
  ThinkingSphinx::Search.search(*args)
30
30
  end
31
-
31
+
32
+ def search_count(*args)
33
+ options = args.extract_options!
34
+ options[:class] = self
35
+ args << options
36
+ ThinkingSphinx::Search.count(*args)
37
+ end
38
+
32
39
  def search_for_id(*args)
33
40
  options = args.extract_options!
34
41
  options[:class] = self
@@ -0,0 +1,27 @@
1
+ module ThinkingSphinx
2
+ class AbstractAdapter
3
+ class << self
4
+ def setup
5
+ # Deliberately blank - subclasses should do something though. Well, if
6
+ # they need to.
7
+ end
8
+
9
+ def detect(model)
10
+ case model.connection.class.name
11
+ when "ActiveRecord::ConnectionAdapters::MysqlAdapter"
12
+ ThinkingSphinx::MysqlAdapter
13
+ when "ActiveRecord::ConnectionAdapters::PostgreSQLAdapter"
14
+ ThinkingSphinx::PostgreSQLAdapter
15
+ else
16
+ raise "Invalid Database Adapter: Sphinx only supports MySQL and PostgreSQL"
17
+ end
18
+ end
19
+
20
+ protected
21
+
22
+ def connection
23
+ @connection ||= ::ActiveRecord::Base.connection
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,9 @@
1
+ module ThinkingSphinx
2
+ class MysqlAdapter < AbstractAdapter
3
+ class << self
4
+ def setup
5
+ # Does MySQL actually need to do anything?
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,84 @@
1
+ module ThinkingSphinx
2
+ class PostgreSQLAdapter < AbstractAdapter
3
+ class << self
4
+ def setup
5
+ create_array_accum_function
6
+ create_crc32_function
7
+ end
8
+
9
+ private
10
+
11
+ def create_array_accum_function
12
+ connection.execute "begin"
13
+ connection.execute "savepoint ts"
14
+ begin
15
+ # See http://www.postgresql.org/docs/8.2/interactive/sql-createaggregate.html
16
+ if connection.raw_connection.server_version > 80200
17
+ connection.execute <<-SQL
18
+ CREATE AGGREGATE array_accum (anyelement)
19
+ (
20
+ sfunc = array_append,
21
+ stype = anyarray,
22
+ initcond = '{}'
23
+ );
24
+ SQL
25
+ else
26
+ connection.execute <<-SQL
27
+ CREATE AGGREGATE array_accum
28
+ (
29
+ basetype = anyelement,
30
+ sfunc = array_append,
31
+ stype = anyarray,
32
+ initcond = '{}'
33
+ );
34
+ SQL
35
+ end
36
+ rescue
37
+ connection.execute "rollback to savepoint ts"
38
+ end
39
+ connection.execute "release savepoint ts"
40
+ connection.execute "commit"
41
+ end
42
+
43
+ def create_crc32_function
44
+ connection.execute "begin"
45
+ connection.execute "savepoint ts"
46
+ begin
47
+ connection.execute "CREATE LANGUAGE 'plpgsql';"
48
+ connection.execute <<-SQL
49
+ CREATE OR REPLACE FUNCTION crc32(word text)
50
+ RETURNS bigint AS $$
51
+ DECLARE tmp bigint;
52
+ DECLARE i int;
53
+ DECLARE j int;
54
+ BEGIN
55
+ i = 0;
56
+ tmp = 4294967295;
57
+ LOOP
58
+ tmp = (tmp # get_byte(word::bytea, i))::bigint;
59
+ i = i + 1;
60
+ j = 0;
61
+ LOOP
62
+ tmp = ((tmp >> 1) # (3988292384 * (tmp & 1)))::bigint;
63
+ j = j + 1;
64
+ IF j >= 8 THEN
65
+ EXIT;
66
+ END IF;
67
+ END LOOP;
68
+ IF i >= char_length(word) THEN
69
+ EXIT;
70
+ END IF;
71
+ END LOOP;
72
+ return (tmp # 4294967295);
73
+ END
74
+ $$ IMMUTABLE STRICT LANGUAGE plpgsql;
75
+ SQL
76
+ rescue
77
+ connection.execute "rollback to savepoint ts"
78
+ end
79
+ connection.execute "release savepoint ts"
80
+ connection.execute "commit"
81
+ end
82
+ end
83
+ end
84
+ end
@@ -95,6 +95,10 @@ module ThinkingSphinx
95
95
  (parent ? parent.ancestors : []) << self
96
96
  end
97
97
 
98
+ def has_column?(column)
99
+ @reflection.klass.column_names.include?(column.to_s)
100
+ end
101
+
98
102
  private
99
103
 
100
104
  # Returns all the objects that could be currently instantiated from a