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
data/test/test_config.rb CHANGED
@@ -1,41 +1,75 @@
1
- require 'helper'
1
+ require 'test/unit'
2
+ require 'dm-sphinx-adapter'
2
3
 
3
4
  class TestConfig < Test::Unit::TestCase
4
5
  def setup
5
- data = Pathname(__FILE__).dirname.expand_path / 'data'
6
- @config = data / 'sphinx.conf'
7
- @log = data / 'sphinx.log'
6
+ base = Pathname(__FILE__).dirname.expand_path
7
+ @config = base / 'files' / 'sphinx.conf'
8
+ @log = base / 'var' / 'sphinx.log'
8
9
  end
9
10
 
10
11
  def test_initialize
11
- assert_nothing_raised{ config_new }
12
- assert_raise(IOError){ config_new(:config => nil) }
13
- assert_raise(IOError){ config_new(:config => 'blah') }
14
- assert_raise(IOError){ config_new(:config => @log) }
12
+ assert_nothing_raised{ config }
13
+ assert_nothing_raised{ config({}) }
14
+ assert_nothing_raised{ config(:config => nil) }
15
+ assert_nothing_raised{ config(:config => 'blah') }
16
+ assert_nothing_raised{ config(:config => @log) }
15
17
  end
16
18
 
17
- def test_initalize_forms
18
- assert_nothing_raised{ config_new(:database => @config) }
19
- # TODO: DataObjects::URI treats /test as the hostname.
20
- # assert_nothing_raised{ config_new('file://test/data/sphinx.conf') }
21
- assert_nothing_raised{ config_new('sphinx://localhost/test/data/sphinx.conf') }
19
+ def test_initalize_defaults
20
+ assert c = config({})
21
+ assert_equal '0.0.0.0', c.address
22
+ assert_equal 3312, c.port
23
+ assert_equal 'searchd.log', c.log
24
+ assert_raise(RuntimeError){ c.pid_file }
25
+ assert_nil c.config
22
26
  end
23
27
 
24
- def test_config
25
- assert_equal @config, config_new.config
28
+ def test_initalize_options_hash
29
+ assert c = config(
30
+ :host => 'options',
31
+ :port => 1234,
32
+ :log => 'log.log',
33
+ :pid_file => 'pid.pid'
34
+ )
35
+
36
+ assert_equal 'options', c.address
37
+ assert_equal 1234, c.port
38
+ assert_equal 'log.log', c.log
39
+ assert_equal 'pid.pid', c.pid_file
40
+ assert_nil c.config
41
+ end
42
+
43
+ def test_initialize_options_string
44
+ assert c = config("sphinx://options:1234")
45
+ assert_equal 'options', c.address
46
+ assert_equal 1234, c.port
47
+ assert_equal 'searchd.log', c.log
48
+ assert_raise(RuntimeError){ c.pid_file }
49
+ assert_nil c.config
50
+ end
51
+
52
+ def test_initialize_config
53
+ assert c = config(:config => @config)
54
+ assert_equal 'localhost', c.address
55
+ assert_equal '3312', c.port
56
+ assert_equal 'test/var/sphinx.log', c.log
57
+ assert_equal 'test/var/sphinx.pid', c.pid_file
58
+ assert_equal @config, c.config
59
+ end
60
+
61
+ def test_initialize_database_hash
62
+ assert c = config(:database => @config)
63
+ assert_equal @config, c.config
26
64
  end
27
65
 
28
- def test_searchd
29
- assert_kind_of Hash, config_new.searchd
30
- assert_equal 'localhost', config_new.address
31
- assert_equal '3312', config_new.port
32
- assert_equal 'test/data/sphinx.pid', config_new.pid_file
33
- assert_equal 'test/data/sphinx.log', config_new.log
66
+ def test_initialize_database_string
67
+ assert c = config("sphinx://localhost/test/files/sphinx.conf")
68
+ assert_equal @config, c.config
34
69
  end
35
70
 
36
71
  protected
37
- def config_new(options = {:config => @config})
38
- DataMapper::SphinxConfig.new(options)
72
+ def config(options = {:config => @config})
73
+ DataMapper::Adapters::Sphinx::Config.new(options)
39
74
  end
40
-
41
75
  end # TestConfig
@@ -0,0 +1,29 @@
1
+ require 'test/unit'
2
+ require 'dm-sphinx-adapter'
3
+
4
+ class TestConfigParser < Test::Unit::TestCase
5
+ def setup
6
+ base = Pathname(__FILE__).dirname.expand_path
7
+ @config = base / 'files' / 'sphinx.conf'
8
+ @log = base / 'var' / 'sphinx.log'
9
+ end
10
+
11
+ def test_parse
12
+ assert_nothing_raised{ parse }
13
+ assert_raise(Errno::ENOENT){ parse('blah') }
14
+ assert_raise(RuntimeError){ parse(@log) }
15
+ end
16
+
17
+ def test_searchd
18
+ assert_kind_of Hash, searchd = parse
19
+ assert_equal 'localhost', searchd['address']
20
+ assert_equal '3312', searchd['port']
21
+ assert_equal 'test/var/sphinx.pid', searchd['pid_file']
22
+ assert_equal 'test/var/sphinx.log', searchd['log']
23
+ end
24
+
25
+ protected
26
+ def parse(config = @config)
27
+ DataMapper::Adapters::Sphinx::ConfigParser.parse(config)
28
+ end
29
+ end # TestConfigParser
@@ -0,0 +1,47 @@
1
+ require 'test/unit'
2
+ require 'dm-sphinx-adapter'
3
+ require 'files/resource_explicit'
4
+
5
+ class TestQuery < Test::Unit::TestCase
6
+ def setup
7
+ DataMapper.setup(:default, :adapter => 'sphinx')
8
+ @repository = repository(:default)
9
+ end
10
+
11
+ def test_initialize
12
+ assert_nothing_raised{ query }
13
+ assert_equal '', query.to_s
14
+ end
15
+
16
+ def test_eql
17
+ assert_equal '@name "foo"', query(:name => 'foo').to_s
18
+ assert_equal '@name "foo"', query(:name.eql => 'foo').to_s
19
+ assert_equal '@name "foo"', query(:name.like => 'foo').to_s
20
+ assert_equal '@name "foo bar"', query(:name => %w(foo bar)).to_s
21
+ end
22
+
23
+ def test_not
24
+ assert_equal '@name -"foo"', query(:name.not => 'foo').to_s
25
+ assert_equal '@name -"foo bar"', query(:name.not => %w(foo bar)).to_s
26
+ end
27
+
28
+ def test_in
29
+ assert_equal '@name ("foo" | "bar")', query(:name.in => %w{foo bar}).to_s
30
+ end
31
+
32
+ def test_and
33
+ # When is DM going to switch conditions to an array? :(
34
+ assert /(?:@name "b" )?@name "a"(?: @name "b")?/.match(query(:name.eql => 'a', :name.eql => 'b').to_s)
35
+ end
36
+
37
+ def test_raw
38
+ assert_equal '"foo bar"~10', query(:conditions => ['"foo bar"~10']).to_s
39
+ end
40
+
41
+ protected
42
+ def query(conditions = {})
43
+ DataMapper::Adapters::Sphinx::Query.new(
44
+ DataMapper::Query.new(@repository, Explicit, conditions)
45
+ )
46
+ end
47
+ end # TestQuery
@@ -0,0 +1,8 @@
1
+ require 'dm-sphinx-adapter'
2
+
3
+ class TestTypeAttribute < Test::Unit::TestCase
4
+ def test_initialize
5
+ # TODO:
6
+ assert true
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ require 'dm-sphinx-adapter'
2
+
3
+ class TestTypeIndex < Test::Unit::TestCase
4
+ def test_initialize
5
+ # TODO:
6
+ assert true
7
+ end
8
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dm-sphinx-adapter
3
3
  version: !ruby/object:Gem::Version
4
- version: "0.4"
4
+ version: "0.5"
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shane Hanna
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-11-20 00:00:00 +11:00
12
+ date: 2008-12-01 00:00:00 +11:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -42,7 +42,7 @@ dependencies:
42
42
  - !ruby/object:Gem::Version
43
43
  version: 1.8.2
44
44
  version:
45
- description: ""
45
+ description: A DataMapper Sphinx adapter.
46
46
  email:
47
47
  - shane.hanna@gmail.com
48
48
  executables: []
@@ -62,23 +62,34 @@ files:
62
62
  - Rakefile
63
63
  - dm-sphinx-adapter.gemspec
64
64
  - lib/dm-sphinx-adapter.rb
65
- - lib/dm-sphinx-adapter/sphinx_adapter.rb
66
- - lib/dm-sphinx-adapter/sphinx_attribute.rb
67
- - lib/dm-sphinx-adapter/sphinx_client.rb
68
- - lib/dm-sphinx-adapter/sphinx_config.rb
69
- - lib/dm-sphinx-adapter/sphinx_index.rb
70
- - lib/dm-sphinx-adapter/sphinx_resource.rb
71
- - test/data/sphinx.conf
72
- - test/fixtures/item.rb
73
- - test/fixtures/item.sql
74
- - test/fixtures/item_resource_explicit.rb
75
- - test/fixtures/item_resource_only.rb
76
- - test/helper.rb
65
+ - lib/dm-sphinx-adapter/adapter.rb
66
+ - lib/dm-sphinx-adapter/attribute.rb
67
+ - lib/dm-sphinx-adapter/client.rb
68
+ - lib/dm-sphinx-adapter/config.rb
69
+ - lib/dm-sphinx-adapter/config_parser.rb
70
+ - lib/dm-sphinx-adapter/index.rb
71
+ - lib/dm-sphinx-adapter/query.rb
72
+ - lib/dm-sphinx-adapter/resource.rb
73
+ - test/files/dm_sphinx_adapter_test.sql
74
+ - test/files/resource_explicit.rb
75
+ - test/files/resource_resource.rb
76
+ - test/files/resource_searchable.rb
77
+ - test/files/resource_storage_name.rb
78
+ - test/files/resource_vanilla.rb
79
+ - test/files/sphinx.conf
80
+ - test/test_adapter.rb
81
+ - test/test_adapter_explicit.rb
82
+ - test/test_adapter_resource.rb
83
+ - test/test_adapter_searchable.rb
84
+ - test/test_adapter_vanilla.rb
77
85
  - test/test_client.rb
78
86
  - test/test_config.rb
79
- - test/test_search.rb
87
+ - test/test_config_parser.rb
88
+ - test/test_query.rb
89
+ - test/test_type_attribute.rb
90
+ - test/test_type_index.rb
80
91
  has_rdoc: true
81
- homepage: A Sphinx DataMapper adapter.
92
+ homepage: http://dm-sphinx.rubyforge.org
82
93
  post_install_message:
83
94
  rdoc_options:
84
95
  - --main
@@ -103,8 +114,16 @@ rubyforge_project: dm-sphinx-adapter
103
114
  rubygems_version: 1.3.0
104
115
  signing_key:
105
116
  specification_version: 2
106
- summary: ""
117
+ summary: A DataMapper Sphinx adapter.
107
118
  test_files:
119
+ - test/test_adapter.rb
120
+ - test/test_adapter_explicit.rb
121
+ - test/test_adapter_resource.rb
122
+ - test/test_adapter_searchable.rb
123
+ - test/test_adapter_vanilla.rb
108
124
  - test/test_client.rb
109
125
  - test/test_config.rb
110
- - test/test_search.rb
126
+ - test/test_config_parser.rb
127
+ - test/test_query.rb
128
+ - test/test_type_attribute.rb
129
+ - test/test_type_index.rb
@@ -1,220 +0,0 @@
1
- require 'benchmark'
2
-
3
- # TODO: I think perhaps I should move all the query building code to a lib of its own.
4
-
5
- module DataMapper
6
- module Adapters
7
-
8
- # == Synopsis
9
- # DataMapper uses URIs or a connection has to connect to your data-stores. In this case the sphinx search daemon
10
- # <tt>searchd</tt>.
11
- #
12
- # On its own this adapter will only return an array of document IDs when queried. The dm-more source (not the gem)
13
- # however provides dm-is-searchable, a common interface to search one adapter and load documents from another. My
14
- # suggestion is to use this adapter in tandem with dm-is-searchable.
15
- #
16
- # The dm-is-searchable plugin is part of dm-more though unfortunately isn't built and bundled with dm-more gem.
17
- # You'll need to checkout the dm-more source with Git from git://github.com/sam/dm-more.git and build/install the
18
- # gem yourself.
19
- #
20
- # git clone git://github.com/sam/dm-more.git
21
- # cd dm-more/dm-is-searchable
22
- # sudo rake install_gem
23
- #
24
- # Like all DataMapper adapters you can connect with a Hash or URI.
25
- #
26
- # A URI:
27
- # DataMapper.setup(:search, 'sphinx://localhost')
28
- #
29
- # The breakdown is:
30
- # "#{adapter}://#{host}:#{port}/#{config}"
31
- # - adapter Must be :sphinx
32
- # - host Hostname (default: localhost)
33
- # - port Optional port number (default: 3312)
34
- # - config Optional but recommended path to sphinx config file.
35
- #
36
- # Alternatively supply a Hash:
37
- # DataMapper.setup(:search, {
38
- # :adapter => 'sphinx', # required
39
- # :config => './sphinx.conf' # optional. Recommended though.
40
- # :host => 'localhost', # optional. Default: localhost
41
- # :port => 3312 # optional. Default: 3312
42
- # :managed => true # optional. Self managed searchd server using daemon_controller.
43
- # })
44
- class SphinxAdapter < AbstractAdapter
45
- ##
46
- # Initialize the sphinx adapter.
47
- #
48
- # @param [URI, DataObject::URI, Addressable::URI, String, Hash, Pathname] uri_or_options
49
- # @see DataMapper::SphinxConfig
50
- # @see DataMapper::SphinxClient
51
- def initialize(name, uri_or_options)
52
- super
53
-
54
- managed = !!(uri_or_options.kind_of?(Hash) && uri_or_options[:managed])
55
- @client = managed ? SphinxManagedClient.new(uri_or_options) : SphinxClient.new(uri_or_options)
56
- end
57
-
58
- ##
59
- # Interaction with searchd and indexer.
60
- #
61
- # @see DataMapper::SphinxClient
62
- # @see DataMapper::SphinxManagedClient
63
- attr_reader :client
64
-
65
- def create(resources) #:nodoc:
66
- true
67
- end
68
-
69
- def delete(query) #:nodoc:
70
- true
71
- end
72
-
73
- def read_many(query)
74
- read(query)
75
- end
76
-
77
- def read_one(query)
78
- read(query).first
79
- end
80
-
81
- protected
82
- ##
83
- # List sphinx indexes to search.
84
- # If no indexes are explicitly declared using DataMapper::SphinxResource then the tableized model name is used.
85
- #
86
- # @see DataMapper::SphinxResource#sphinx_indexes
87
- def indexes(model)
88
- indexes = model.sphinx_indexes(repository(self.name).name) if model.respond_to?(:sphinx_indexes)
89
- if indexes.nil? or indexes.empty?
90
- indexes = [SphinxIndex.new(model, model.storage_name)]
91
- end
92
- indexes
93
- end
94
-
95
- ##
96
- # List sphinx delta indexes to search.
97
- #
98
- # @see DataMapper::SphinxResource#sphinx_indexes
99
- def delta_indexes(model)
100
- indexes(model).find_all{|i| i.delta?}
101
- end
102
-
103
- ##
104
- # Query sphinx for a list of document IDs.
105
- #
106
- # @param [DataMapper::Query]
107
- def read(query)
108
- from = indexes(query.model).map{|index| index.name}.join(', ')
109
- search = search_query(query)
110
- options = {
111
- :match_mode => :extended, # TODO: Modes!
112
- :filters => search_filters(query) # By attribute.
113
- }
114
- options[:limit] = query.limit.to_i if query.limit
115
- options[:offset] = query.offset.to_i if query.offset
116
-
117
- if order = search_order(query)
118
- options.update(
119
- :sort_mode => :extended,
120
- :sort_by => order
121
- )
122
- end
123
-
124
- res = @client.search(search, from, options)
125
- raise res[:error] unless res[:error].nil?
126
-
127
- DataMapper.logger.info(
128
- %q{Sphinx (%.3f): search '%s' in '%s' found %d documents} % [res[:time], search, from, res[:total]]
129
- )
130
- res[:matches].map{|doc| {:id => doc[:doc]}}
131
- end
132
-
133
- ##
134
- # Sphinx search query string from properties (fields).
135
- #
136
- # If the query has no conditions an '' empty string will be generated possibly triggering Sphinx's full scan
137
- # mode.
138
- #
139
- # @see http://www.sphinxsearch.com/doc.html#searching
140
- # @see http://www.sphinxsearch.com/doc.html#conf-docinfo
141
- # @param [DataMapper::Query]
142
- # @return [String]
143
- def search_query(query)
144
- match = []
145
-
146
- if query.conditions.empty?
147
- match << ''
148
- else
149
- # TODO: This needs to be altered by match mode since not everything is supported in different match modes.
150
- query.conditions.each do |operator, property, value|
151
- next if property.kind_of? SphinxAttribute # Filters are added elsewhere.
152
- # TODO: Why does my gem riddle differ from the vendor riddle that comes with ts?
153
- # escaped_value = Riddle.escape(value)
154
- escaped_value = value.to_s.gsub(/[\(\)\|\-!@~"&\/]/){|char| "\\#{char}"}
155
- match << case operator
156
- when :eql, :like then "@#{property.field} #{escaped_value}"
157
- when :not then "@#{property.field} -#{escaped_value}"
158
- when :lt, :gt, :lte, :gte
159
- DataMapper.logger.warn('Sphinx: Query properties with lt, gt, lte, gte are treated as .eql')
160
- "@#{name} #{escaped_value}"
161
- when :raw
162
- "#{property}"
163
- end
164
- end
165
- end
166
- match.join(' ')
167
- end
168
-
169
- ##
170
- # Sphinx search query filters from attributes.
171
- # @param [DataMapper::Query]
172
- # @return [Array]
173
- def search_filters(query)
174
- filters = []
175
- query.conditions.each do |operator, attribute, value|
176
- next unless attribute.kind_of? SphinxAttribute
177
- # TODO: Value cast to uint, bool, str2ordinal, float
178
- filters << case operator
179
- when :eql, :like then Riddle::Client::Filter.new(attribute.name.to_s, filter_value(value))
180
- when :not then Riddle::Client::Filter.new(attribute.name.to_s, filter_value(value), true)
181
- else
182
- error = "Sphinx: Query attributes do not support the #{operator} operator"
183
- DataMapper.logger.error(error)
184
- raise error # TODO: RuntimeError subclass and more information about the actual query.
185
- end
186
- end
187
- filters
188
- end
189
-
190
- ##
191
- # Order by attributes.
192
- #
193
- # @return [String or Symbol]
194
- def search_order(query)
195
- by = []
196
- # TODO: How do you tell the difference between the default query order and someone explicitly asking for
197
- # sorting by the primary key?
198
- query.order.each do |order|
199
- next unless order.property.kind_of? SphinxAttribute
200
- by << [order.property.field, order.direction].join(' ')
201
- end
202
- by.empty? ? nil : by.join(', ')
203
- end
204
-
205
- # TODO: Move this to SphinxAttribute#something.
206
- # This is ninja'd straight from TS just to get things going.
207
- def filter_value(value)
208
- case value
209
- when Range
210
- value.first.is_a?(Time) ? value.first.to_i..value.last.to_i : value
211
- when Array
212
- value.collect { |val| val.is_a?(Time) ? val.to_i : val }
213
- else
214
- Array(value)
215
- end
216
- end
217
- end # SphinxAdapter
218
- end # Adapters
219
- end # DataMapper
220
-