freelancing-god-thinking-sphinx 0.9.8 → 0.9.10

Sign up to get free protection for your applications and to get access to all the features.
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