dm-sphinx-adapter 0.4 → 0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. data/History.txt +12 -0
  2. data/Manifest.txt +24 -13
  3. data/README.txt +44 -28
  4. data/Rakefile +1 -1
  5. data/dm-sphinx-adapter.gemspec +7 -7
  6. data/lib/dm-sphinx-adapter.rb +8 -6
  7. data/lib/dm-sphinx-adapter/adapter.rb +218 -0
  8. data/lib/dm-sphinx-adapter/attribute.rb +38 -0
  9. data/lib/dm-sphinx-adapter/client.rb +109 -0
  10. data/lib/dm-sphinx-adapter/config.rb +122 -0
  11. data/lib/dm-sphinx-adapter/config_parser.rb +71 -0
  12. data/lib/dm-sphinx-adapter/index.rb +38 -0
  13. data/lib/dm-sphinx-adapter/query.rb +67 -0
  14. data/lib/dm-sphinx-adapter/resource.rb +103 -0
  15. data/test/{fixtures/item.sql → files/dm_sphinx_adapter_test.sql} +3 -3
  16. data/test/{fixtures/item_resource_explicit.rb → files/resource_explicit.rb} +5 -6
  17. data/test/{fixtures/item_resource_only.rb → files/resource_resource.rb} +4 -5
  18. data/test/{fixtures/item.rb → files/resource_searchable.rb} +8 -5
  19. data/test/files/resource_storage_name.rb +11 -0
  20. data/test/files/resource_vanilla.rb +7 -0
  21. data/test/{data → files}/sphinx.conf +11 -6
  22. data/test/test_adapter.rb +38 -0
  23. data/test/test_adapter_explicit.rb +48 -0
  24. data/test/test_adapter_resource.rb +25 -0
  25. data/test/test_adapter_searchable.rb +23 -0
  26. data/test/test_adapter_vanilla.rb +46 -0
  27. data/test/test_client.rb +9 -21
  28. data/test/test_config.rb +58 -24
  29. data/test/test_config_parser.rb +29 -0
  30. data/test/test_query.rb +47 -0
  31. data/test/test_type_attribute.rb +8 -0
  32. data/test/test_type_index.rb +8 -0
  33. metadata +38 -19
  34. data/lib/dm-sphinx-adapter/sphinx_adapter.rb +0 -220
  35. data/lib/dm-sphinx-adapter/sphinx_attribute.rb +0 -22
  36. data/lib/dm-sphinx-adapter/sphinx_client.rb +0 -81
  37. data/lib/dm-sphinx-adapter/sphinx_config.rb +0 -160
  38. data/lib/dm-sphinx-adapter/sphinx_index.rb +0 -21
  39. data/lib/dm-sphinx-adapter/sphinx_resource.rb +0 -88
  40. data/test/helper.rb +0 -7
  41. 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
- ('foo', 'I really like foo!', now()),
20
- ('bar', 'I really like bar!', now()),
21
- ('baz', 'I really like baz!', now());
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 ItemResourceExplicit
4
+ class Explicit
6
5
  include DataMapper::Resource
7
6
  include DataMapper::SphinxResource
8
7
 
9
8
  property :id, Serial
10
- property :name, String, :nullable => false, :length => 50
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 :updated, DateTime
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 # ItemResourceExplicit
25
+ end # Explicit
@@ -1,14 +1,13 @@
1
1
  require 'rubygems'
2
2
  require 'dm-is-searchable'
3
- require 'zlib'
4
3
 
5
- class ItemResourceOnly
4
+ class Resource
6
5
  include DataMapper::Resource
7
6
  include DataMapper::SphinxResource
8
7
 
9
8
  property :id, Serial
10
- property :name, String, :nullable => false, :length => 50
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 # ItemResourceOnly
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 Item
4
+ class Searchable
6
5
  include DataMapper::Resource
7
6
  property :id, Serial
8
- property :name, String, :nullable => false, :length => 50
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
- end # Item
12
+
13
+ def self.default_storage_name
14
+ 'item'
15
+ end
16
+ end # Searchable
@@ -0,0 +1,11 @@
1
+ class StorageName
2
+ include DataMapper::Resource
3
+ property :id, Serial
4
+ property :name, String
5
+ property :likes, Text, :lazy => false
6
+ property :updated_on, DateTime
7
+
8
+ def self.default_storage_name
9
+ 'item'
10
+ end
11
+ end # StorageName
@@ -0,0 +1,7 @@
1
+ class Vanilla
2
+ include DataMapper::Resource
3
+ property :id, Serial
4
+ property :name, String
5
+ property :likes, Text, :lazy => false
6
+ property :updated_on, DateTime
7
+ end # Vanilla
@@ -9,11 +9,10 @@ searchd
9
9
  {
10
10
  address = localhost
11
11
  port = 3312
12
- log = test/data/sphinx.log
13
- query_log = test/data/sphinx.query.log
12
+ log = test/var/sphinx.log
13
+ query_log = test/var/sphinx.query.log
14
14
  read_timeout = 5
15
- max_children = 30
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/data/sphinx/items
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/data/sphinx/items_delta
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 'helper'
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 do
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::SphinxClient.new(@config)
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 do
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::SphinxManagedClient.new(@config)
22
+ client = DataMapper::Adapters::Sphinx::ManagedClient.new(@config)
37
23
  client.index
38
- assert client.search('foo')
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