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.
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