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.
- data/README +20 -1
- data/lib/thinking_sphinx.rb +30 -2
- data/lib/thinking_sphinx/active_record.rb +25 -11
- data/lib/thinking_sphinx/active_record/delta.rb +46 -53
- data/lib/thinking_sphinx/active_record/has_many_association.rb +1 -1
- data/lib/thinking_sphinx/active_record/search.rb +8 -1
- data/lib/thinking_sphinx/adapters/abstract_adapter.rb +27 -0
- data/lib/thinking_sphinx/adapters/mysql_adapter.rb +9 -0
- data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +84 -0
- data/lib/thinking_sphinx/association.rb +4 -0
- data/lib/thinking_sphinx/attribute.rb +4 -2
- data/lib/thinking_sphinx/collection.rb +105 -0
- data/lib/thinking_sphinx/configuration.rb +112 -75
- data/lib/thinking_sphinx/field.rb +11 -3
- data/lib/thinking_sphinx/index.rb +119 -26
- data/lib/thinking_sphinx/index/builder.rb +30 -22
- data/lib/thinking_sphinx/index/faux_column.rb +13 -0
- data/lib/thinking_sphinx/rails_additions.rb +13 -1
- data/lib/thinking_sphinx/search.rb +40 -81
- data/spec/unit/thinking_sphinx/active_record/delta_spec.rb +73 -127
- data/spec/unit/thinking_sphinx/active_record/has_many_association_spec.rb +2 -2
- data/spec/unit/thinking_sphinx/active_record/search_spec.rb +26 -0
- data/spec/unit/thinking_sphinx/active_record_spec.rb +94 -22
- data/spec/unit/thinking_sphinx/attribute_spec.rb +8 -4
- data/spec/unit/thinking_sphinx/collection_spec.rb +71 -0
- data/spec/unit/thinking_sphinx/configuration_spec.rb +149 -113
- data/spec/unit/thinking_sphinx/field_spec.rb +13 -4
- data/spec/unit/thinking_sphinx/index/builder_spec.rb +1 -0
- data/spec/unit/thinking_sphinx/index/faux_column_spec.rb +27 -0
- data/spec/unit/thinking_sphinx/index_spec.rb +79 -29
- data/spec/unit/thinking_sphinx/search_spec.rb +114 -74
- data/spec/unit/thinking_sphinx_spec.rb +21 -0
- data/tasks/thinking_sphinx_tasks.rb +24 -10
- metadata +21 -8
- data/lib/riddle.rb +0 -26
- data/lib/riddle/client.rb +0 -639
- data/lib/riddle/client/filter.rb +0 -44
- data/lib/riddle/client/message.rb +0 -65
- data/lib/riddle/client/response.rb +0 -84
- 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
|
data/lib/thinking_sphinx.rb
CHANGED
|
@@ -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 =
|
|
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
|
|
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 :
|
|
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 :
|
|
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.
|
|
67
|
+
self.sphinx_indexes ||= []
|
|
68
68
|
index = Index.new(self, &block)
|
|
69
69
|
|
|
70
|
-
self.
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
134
|
+
"#{self.class.sphinx_indexes.first.name}_core",
|
|
126
135
|
['sphinx_deleted'],
|
|
127
|
-
{self.
|
|
136
|
+
{self.sphinx_document_id => 1}
|
|
128
137
|
) if self.in_core_index?
|
|
129
138
|
|
|
130
139
|
client.update(
|
|
131
|
-
"#{self.class.
|
|
140
|
+
"#{self.class.sphinx_indexes.first.name}_delta",
|
|
132
141
|
['sphinx_deleted'],
|
|
133
|
-
{self.
|
|
142
|
+
{self.sphinx_document_id => 1}
|
|
134
143
|
) if ThinkingSphinx.deltas_enabled? &&
|
|
135
|
-
self.class.
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
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.
|
|
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,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
|