dm-sphinx-adapter 0.4 → 0.5
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/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
|