dm-sphinx-adapter 0.4 → 0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +12 -0
- data/Manifest.txt +24 -13
- data/README.txt +44 -28
- data/Rakefile +1 -1
- data/dm-sphinx-adapter.gemspec +7 -7
- data/lib/dm-sphinx-adapter.rb +8 -6
- data/lib/dm-sphinx-adapter/adapter.rb +218 -0
- data/lib/dm-sphinx-adapter/attribute.rb +38 -0
- data/lib/dm-sphinx-adapter/client.rb +109 -0
- data/lib/dm-sphinx-adapter/config.rb +122 -0
- data/lib/dm-sphinx-adapter/config_parser.rb +71 -0
- data/lib/dm-sphinx-adapter/index.rb +38 -0
- data/lib/dm-sphinx-adapter/query.rb +67 -0
- data/lib/dm-sphinx-adapter/resource.rb +103 -0
- data/test/{fixtures/item.sql → files/dm_sphinx_adapter_test.sql} +3 -3
- data/test/{fixtures/item_resource_explicit.rb → files/resource_explicit.rb} +5 -6
- data/test/{fixtures/item_resource_only.rb → files/resource_resource.rb} +4 -5
- data/test/{fixtures/item.rb → files/resource_searchable.rb} +8 -5
- data/test/files/resource_storage_name.rb +11 -0
- data/test/files/resource_vanilla.rb +7 -0
- data/test/{data → files}/sphinx.conf +11 -6
- data/test/test_adapter.rb +38 -0
- data/test/test_adapter_explicit.rb +48 -0
- data/test/test_adapter_resource.rb +25 -0
- data/test/test_adapter_searchable.rb +23 -0
- data/test/test_adapter_vanilla.rb +46 -0
- data/test/test_client.rb +9 -21
- data/test/test_config.rb +58 -24
- data/test/test_config_parser.rb +29 -0
- data/test/test_query.rb +47 -0
- data/test/test_type_attribute.rb +8 -0
- data/test/test_type_index.rb +8 -0
- metadata +38 -19
- data/lib/dm-sphinx-adapter/sphinx_adapter.rb +0 -220
- data/lib/dm-sphinx-adapter/sphinx_attribute.rb +0 -22
- data/lib/dm-sphinx-adapter/sphinx_client.rb +0 -81
- data/lib/dm-sphinx-adapter/sphinx_config.rb +0 -160
- data/lib/dm-sphinx-adapter/sphinx_index.rb +0 -21
- data/lib/dm-sphinx-adapter/sphinx_resource.rb +0 -88
- data/test/helper.rb +0 -7
- data/test/test_search.rb +0 -52
@@ -0,0 +1,103 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module Adapters
|
3
|
+
module Sphinx
|
4
|
+
|
5
|
+
# Declare Sphinx indexes and attributes in your resource.
|
6
|
+
#
|
7
|
+
# model Items
|
8
|
+
# include DataMapper::SphinxResource
|
9
|
+
#
|
10
|
+
# # .. normal properties and such for :default
|
11
|
+
#
|
12
|
+
# repository(:search) do
|
13
|
+
# # Query some_index, some_index_delta in that order.
|
14
|
+
# index :some_index
|
15
|
+
# index :some_index_delta, :delta => true
|
16
|
+
#
|
17
|
+
# # Sortable by some attributes.
|
18
|
+
# attribute :updated_at, DateTime # sql_attr_timestamp
|
19
|
+
# attribute :age, Integer # sql_attr_uint
|
20
|
+
# attribute :deleted, Boolean # sql_attr_bool
|
21
|
+
# end
|
22
|
+
# end
|
23
|
+
module Resource
|
24
|
+
def self.included(model) #:nodoc:
|
25
|
+
model.class_eval do
|
26
|
+
include DataMapper::Resource
|
27
|
+
extend ClassMethods
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
module ClassMethods
|
32
|
+
def self.extended(model) #:nodoc:
|
33
|
+
model.instance_variable_set(:@sphinx_indexes, {})
|
34
|
+
model.instance_variable_set(:@sphinx_attributes, {})
|
35
|
+
end
|
36
|
+
|
37
|
+
# Defines a sphinx index on the resource.
|
38
|
+
#
|
39
|
+
# Indexes are naturally ordered, with delta indexes at the end of the list so that duplicate document IDs in
|
40
|
+
# delta indexes override your main indexes.
|
41
|
+
#
|
42
|
+
# ==== See
|
43
|
+
# * DataMapper::Adapters::Sphinx::Index
|
44
|
+
#
|
45
|
+
# ==== Parameters
|
46
|
+
# name<Symbol>:: The name of a sphinx index to search for this resource.
|
47
|
+
# options<Hash>:: A hash of available index options.
|
48
|
+
def index(name, options = {})
|
49
|
+
index = Index.new(self, name, options)
|
50
|
+
indexes = sphinx_indexes(repository_name)
|
51
|
+
indexes << index
|
52
|
+
|
53
|
+
# TODO: I'm such a Ruby nub. In the meantime I've gone back to my Perl roots.
|
54
|
+
# This is a Schwartzian transform to sort delta indexes to the bottom and natural sort by name.
|
55
|
+
mapped = indexes.map{|i| [(i.delta? ? 1 : 0), i.name, i]}
|
56
|
+
sorted = mapped.sort{|a, b| a[0] <=> b[0] || a[1] <=> b[1]}
|
57
|
+
indexes.replace(sorted.map{|i| i[2]})
|
58
|
+
|
59
|
+
index
|
60
|
+
end
|
61
|
+
|
62
|
+
# List of declared sphinx indexes for this model.
|
63
|
+
#
|
64
|
+
# ==== Returns
|
65
|
+
# Array<DataMapper::Adapters::Sphinx::Index>
|
66
|
+
def sphinx_indexes(repository_name = default_repository_name)
|
67
|
+
@sphinx_indexes[repository_name] ||= []
|
68
|
+
end
|
69
|
+
|
70
|
+
# Defines a sphinx attribute on the resource.
|
71
|
+
#
|
72
|
+
# ==== See
|
73
|
+
# DataMapper::Adapters::Sphinx::Attribute
|
74
|
+
#
|
75
|
+
# ==== Parameters
|
76
|
+
# name<Symbol>:: The name of a sphinx attribute to order/restrict by for this resource.
|
77
|
+
# type<Class>:: The type to define this attribute as.
|
78
|
+
# options<Hash>:: An optional hash of attribute options.
|
79
|
+
def attribute(name, type, options = {})
|
80
|
+
# Attributes are just properties without a getter/setter in the model.
|
81
|
+
# This keeps DataMapper::Query happy when building queries.
|
82
|
+
attribute = Sphinx::Attribute.new(self, name, type, options)
|
83
|
+
properties(repository_name)[attribute.name] = attribute
|
84
|
+
attribute
|
85
|
+
end
|
86
|
+
|
87
|
+
# List of declared sphinx attributes for this model.
|
88
|
+
#
|
89
|
+
# ==== Returns
|
90
|
+
# Array<DataMapper::Adapters::Sphinx::Attribute>
|
91
|
+
def sphinx_attributes(repository_name = default_repository_name)
|
92
|
+
properties(repository_name).grep{|p| p.kind_of? Sphinx::Attribute}
|
93
|
+
end
|
94
|
+
|
95
|
+
end # ClassMethods
|
96
|
+
end # Resource
|
97
|
+
end # Sphinx
|
98
|
+
end # Adapters
|
99
|
+
|
100
|
+
# Follow DM naming convention.
|
101
|
+
SphinxResource = Adapters::Sphinx::Resource
|
102
|
+
end # DataMapper
|
103
|
+
|
@@ -16,6 +16,6 @@ create table items (
|
|
16
16
|
) engine=innodb default charset=utf8;
|
17
17
|
|
18
18
|
insert into items (name, likes, updated_on) values
|
19
|
-
('
|
20
|
-
('
|
21
|
-
('
|
19
|
+
('one', 'I really like one!', now()),
|
20
|
+
('two', 'I really like two!', now()),
|
21
|
+
('three', 'I really like three!', now());
|
@@ -1,14 +1,13 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'dm-is-searchable'
|
3
|
-
require 'zlib'
|
4
3
|
|
5
|
-
class
|
4
|
+
class Explicit
|
6
5
|
include DataMapper::Resource
|
7
6
|
include DataMapper::SphinxResource
|
8
7
|
|
9
8
|
property :id, Serial
|
10
|
-
property :name, String
|
11
|
-
property :likes, Text
|
9
|
+
property :name, String
|
10
|
+
property :likes, Text, :lazy => false
|
12
11
|
property :updated_on, DateTime
|
13
12
|
|
14
13
|
is :searchable
|
@@ -17,10 +16,10 @@ class ItemResourceExplicit
|
|
17
16
|
index :items
|
18
17
|
index :items_delta, :delta => true
|
19
18
|
property :name, String
|
20
|
-
attribute :
|
19
|
+
attribute :updated_on, DateTime
|
21
20
|
end
|
22
21
|
|
23
22
|
def self.default_storage_name
|
24
23
|
'item'
|
25
24
|
end
|
26
|
-
end #
|
25
|
+
end # Explicit
|
@@ -1,14 +1,13 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'dm-is-searchable'
|
3
|
-
require 'zlib'
|
4
3
|
|
5
|
-
class
|
4
|
+
class Resource
|
6
5
|
include DataMapper::Resource
|
7
6
|
include DataMapper::SphinxResource
|
8
7
|
|
9
8
|
property :id, Serial
|
10
|
-
property :name, String
|
11
|
-
property :likes, Text
|
9
|
+
property :name, String
|
10
|
+
property :likes, Text, :lazy => false
|
12
11
|
property :updated_on, DateTime
|
13
12
|
|
14
13
|
is :searchable
|
@@ -16,5 +15,5 @@ class ItemResourceOnly
|
|
16
15
|
def self.default_storage_name
|
17
16
|
'item'
|
18
17
|
end
|
19
|
-
end #
|
18
|
+
end # Resource
|
20
19
|
|
@@ -1,13 +1,16 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'dm-is-searchable'
|
3
|
-
require 'zlib'
|
4
3
|
|
5
|
-
class
|
4
|
+
class Searchable
|
6
5
|
include DataMapper::Resource
|
7
6
|
property :id, Serial
|
8
|
-
property :name, String
|
9
|
-
property :likes, Text
|
7
|
+
property :name, String
|
8
|
+
property :likes, Text, :lazy => false
|
10
9
|
property :updated_on, DateTime
|
11
10
|
|
12
11
|
is :searchable
|
13
|
-
|
12
|
+
|
13
|
+
def self.default_storage_name
|
14
|
+
'item'
|
15
|
+
end
|
16
|
+
end # Searchable
|
@@ -9,11 +9,10 @@ searchd
|
|
9
9
|
{
|
10
10
|
address = localhost
|
11
11
|
port = 3312
|
12
|
-
log = test/
|
13
|
-
query_log = test/
|
12
|
+
log = test/var/sphinx.log
|
13
|
+
query_log = test/var/sphinx.query.log
|
14
14
|
read_timeout = 5
|
15
|
-
|
16
|
-
pid_file = test/data/sphinx.pid
|
15
|
+
pid_file = test/var/sphinx.pid
|
17
16
|
max_matches = 1000
|
18
17
|
}
|
19
18
|
|
@@ -63,11 +62,17 @@ source items_delta : items
|
|
63
62
|
index items
|
64
63
|
{
|
65
64
|
source = items
|
66
|
-
path = test/
|
65
|
+
path = test/var/sphinx/items
|
67
66
|
}
|
68
67
|
|
69
68
|
index items_delta : items
|
70
69
|
{
|
71
70
|
source = items_delta
|
72
|
-
path = test/
|
71
|
+
path = test/var/sphinx/items_delta
|
72
|
+
}
|
73
|
+
|
74
|
+
index vanillas
|
75
|
+
{
|
76
|
+
type = distributed
|
77
|
+
local = items
|
73
78
|
}
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'dm-sphinx-adapter'
|
2
|
+
require 'test/unit'
|
3
|
+
|
4
|
+
# DataMapper::Logger.new(STDOUT, :debug)
|
5
|
+
|
6
|
+
class TestAdapter < Test::Unit::TestCase
|
7
|
+
def setup
|
8
|
+
# TODO: A little too brutal even by my standards.
|
9
|
+
Dir.chdir(File.join(File.dirname(__FILE__), 'files')) do
|
10
|
+
system 'mysql -u root dm_sphinx_adapter_test < dm_sphinx_adapter_test.sql' \
|
11
|
+
or raise %q{Tests require the dm_sphinx_adapter_test database.}
|
12
|
+
end
|
13
|
+
|
14
|
+
DataMapper.setup(:default, 'mysql://localhost/dm_sphinx_adapter_test')
|
15
|
+
|
16
|
+
@config = Pathname.new(__FILE__).dirname.expand_path / 'files' / 'sphinx.conf'
|
17
|
+
@client = DataMapper::Adapters::Sphinx::ManagedClient.new(:config => @config)
|
18
|
+
@client.index
|
19
|
+
sleep 1
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_unmanaged_setup
|
23
|
+
assert DataMapper.setup(:sphinx, :adapter => 'sphinx')
|
24
|
+
assert_kind_of DataMapper::Adapters::SphinxAdapter, repository(:sphinx).adapter
|
25
|
+
assert_kind_of DataMapper::Adapters::Sphinx::Client, repository(:sphinx).adapter.client
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_managed_setup
|
29
|
+
assert DataMapper.setup(:sphinx, :adapter => 'sphinx', :config => @config, :managed => true)
|
30
|
+
assert_kind_of DataMapper::Adapters::SphinxAdapter, repository(:sphinx).adapter
|
31
|
+
assert_kind_of DataMapper::Adapters::Sphinx::ManagedClient, repository(:sphinx).adapter.client
|
32
|
+
end
|
33
|
+
|
34
|
+
def teardown
|
35
|
+
@client.stop
|
36
|
+
sleep 1
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'test_adapter'
|
2
|
+
require 'files/resource_explicit'
|
3
|
+
|
4
|
+
class TestAdapterExplicit < TestAdapter
|
5
|
+
def setup
|
6
|
+
super
|
7
|
+
DataMapper.setup(:search, :adapter => 'sphinx', :config => @config, :managed => true)
|
8
|
+
end
|
9
|
+
|
10
|
+
def teardown
|
11
|
+
DataMapper.repository(:search).adapter.client.stop
|
12
|
+
super
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_initialize
|
16
|
+
assert_nothing_raised{ Explicit.new }
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_search_properties
|
20
|
+
assert_equal Explicit.all, Explicit.search
|
21
|
+
assert_equal [Explicit.first(:id => 2)], Explicit.search(:name => 'two')
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_search_delta
|
25
|
+
resource = Explicit.create(:name => 'four', :likes => 'chicken', :updated_on => Time.now)
|
26
|
+
DataMapper.repository(:search).adapter.client.index('items_delta')
|
27
|
+
assert_equal [resource], Explicit.search(:name => 'four')
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_search_attribute_timestamp
|
31
|
+
time = Time.now
|
32
|
+
resource = Explicit.create(:name => 'four', :likes => 'chicken', :updated_on => time)
|
33
|
+
DataMapper.repository(:search).adapter.client.index('items_delta')
|
34
|
+
|
35
|
+
assert_equal [resource], Explicit.search(:updated_on => time.to_i)
|
36
|
+
assert_equal [resource], Explicit.search(:updated_on => (time .. time + 1))
|
37
|
+
assert_equal [], Explicit.search(:updated_on => (time - 60 * 60))
|
38
|
+
assert_equal [], Explicit.search(:updated_on => (time + 60 * 60))
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_search_attribute_boolean
|
42
|
+
# TODO:
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_search_attribute_integer
|
46
|
+
# TODO
|
47
|
+
end
|
48
|
+
end # TestAdapterExplicit
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'test_adapter'
|
2
|
+
require 'files/resource_resource'
|
3
|
+
|
4
|
+
class TestAdapterResource < TestAdapter
|
5
|
+
def setup
|
6
|
+
super
|
7
|
+
DataMapper.setup(:search, :adapter => 'sphinx', :config => @config, :managed => true)
|
8
|
+
end
|
9
|
+
|
10
|
+
def teardown
|
11
|
+
DataMapper.repository(:search).adapter.client.stop
|
12
|
+
super
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_initialize
|
16
|
+
assert_nothing_raised{ Resource.new }
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_search_properties
|
20
|
+
assert_equal Resource.all, Resource.search
|
21
|
+
assert_equal [Resource.first(:id => 2)], Resource.search(:name => 'two')
|
22
|
+
assert_equal [Resource.first(:id => 2)], Resource.search(:conditions => ['two'])
|
23
|
+
end
|
24
|
+
end # TestAdapterResource
|
25
|
+
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'test_adapter'
|
2
|
+
require 'files/resource_searchable'
|
3
|
+
|
4
|
+
class TestAdapterSearchable < TestAdapter
|
5
|
+
def setup
|
6
|
+
super
|
7
|
+
DataMapper.setup(:search, :adapter => 'sphinx', :config => @config, :managed => true)
|
8
|
+
end
|
9
|
+
|
10
|
+
def teardown
|
11
|
+
DataMapper.repository(:search).adapter.client.stop
|
12
|
+
super
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_initialize
|
16
|
+
assert_nothing_raised{ Searchable.new }
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_search
|
20
|
+
assert_equal Searchable.all, Searchable.search
|
21
|
+
assert_equal [Searchable.first(:id => 2)], Searchable.search(:name => 'two')
|
22
|
+
end
|
23
|
+
end # TestAdapterSearchable
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'test_adapter'
|
2
|
+
require 'files/resource_vanilla'
|
3
|
+
require 'files/resource_storage_name'
|
4
|
+
|
5
|
+
class TestAdapterVanilla < TestAdapter
|
6
|
+
def setup
|
7
|
+
super
|
8
|
+
DataMapper.setup(:default, :adapter => 'sphinx', :config => @config, :managed => true)
|
9
|
+
end
|
10
|
+
|
11
|
+
def teardown
|
12
|
+
DataMapper.repository(:default).adapter.client.stop
|
13
|
+
super
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_initialize
|
17
|
+
assert_nothing_raised{ Vanilla.new }
|
18
|
+
assert_nothing_raised{ StorageName.new }
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_all
|
22
|
+
assert_equal [{:id => 1}, {:id => 2}, {:id => 3}], Vanilla.all
|
23
|
+
assert_equal [{:id => 1}], Vanilla.all(:name => 'one')
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_all_limit
|
27
|
+
assert_equal [{:id => 1}], Vanilla.all(:limit => 1)
|
28
|
+
assert_equal [{:id => 1}, {:id => 2}], Vanilla.all(:limit => 2)
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_all_offset
|
32
|
+
assert_equal [{:id => 1}, {:id => 2}, {:id => 3}], Vanilla.all(:offset => 0)
|
33
|
+
assert_equal [{:id => 2}, {:id => 3}], Vanilla.all(:offset => 1)
|
34
|
+
assert_equal [], Vanilla.all(:offset => 3)
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_first
|
38
|
+
assert_equal({:id => 1}, Vanilla.first(:name => 'one'))
|
39
|
+
assert_nil Vanilla.first(:name => 'missing')
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_storage_name
|
43
|
+
assert_equal Vanilla.all, StorageName.all
|
44
|
+
assert_equal Vanilla.first, StorageName.first
|
45
|
+
end
|
46
|
+
end
|
data/test/test_client.rb
CHANGED
@@ -1,24 +1,12 @@
|
|
1
|
-
require '
|
2
|
-
|
3
|
-
class TestClient < Test::Unit::TestCase
|
4
|
-
def setup
|
5
|
-
@config = Pathname.new(__FILE__).dirname.expand_path / 'data' / 'sphinx.conf'
|
6
|
-
|
7
|
-
# TODO: A little too brutal for me.
|
8
|
-
Dir.chdir(File.join(File.dirname(__FILE__), 'fixtures')) do
|
9
|
-
system 'mysql -u root dm_sphinx_adapter_test < item.sql' \
|
10
|
-
or raise %q{Tests require the dm_sphinx_adapter_test.items table.}
|
11
|
-
end
|
12
|
-
end
|
1
|
+
require 'test_adapter'
|
13
2
|
|
3
|
+
class TestClient < TestAdapter
|
14
4
|
def test_initialize
|
15
|
-
assert_nothing_raised
|
16
|
-
DataMapper::SphinxClient.new(@config)
|
17
|
-
end
|
5
|
+
assert_nothing_raised { DataMapper::Adapters::Sphinx::Client.new(@config) }
|
18
6
|
end
|
19
7
|
|
20
8
|
def test_index
|
21
|
-
client = DataMapper::
|
9
|
+
client = DataMapper::Adapters::Sphinx::Client.new(@config)
|
22
10
|
assert_nothing_raised{ client.index }
|
23
11
|
assert_nothing_raised{ client.index 'items' }
|
24
12
|
assert_nothing_raised{ client.index '*' }
|
@@ -26,16 +14,16 @@ class TestClient < Test::Unit::TestCase
|
|
26
14
|
end
|
27
15
|
|
28
16
|
def test_managed_initialize
|
29
|
-
assert_nothing_raised
|
30
|
-
DataMapper::SphinxManagedClient.new(@config)
|
31
|
-
end
|
17
|
+
assert_nothing_raised { DataMapper::Adapters::Sphinx::ManagedClient.new(@config) }
|
32
18
|
end
|
33
19
|
|
34
20
|
def test_search
|
35
21
|
begin
|
36
|
-
client = DataMapper::
|
22
|
+
client = DataMapper::Adapters::Sphinx::ManagedClient.new(@config)
|
37
23
|
client.index
|
38
|
-
assert client.search('
|
24
|
+
assert match = client.search('two')
|
25
|
+
assert_equal 1, match[:total]
|
26
|
+
assert_equal 2, match[:matches][0][:doc]
|
39
27
|
ensure
|
40
28
|
client.stop
|
41
29
|
end
|