shanna-dm-sphinx-adapter 0.5 → 0.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. data/Manifest.txt +12 -19
  2. data/README.txt +30 -38
  3. data/Rakefile +2 -3
  4. data/dm-sphinx-adapter.gemspec +6 -9
  5. data/lib/dm-sphinx-adapter/adapter.rb +87 -73
  6. data/lib/dm-sphinx-adapter/attribute.rb +56 -12
  7. data/lib/dm-sphinx-adapter/index.rb +14 -1
  8. data/lib/dm-sphinx-adapter/query.rb +19 -13
  9. data/lib/dm-sphinx-adapter/resource.rb +20 -13
  10. data/lib/dm-sphinx-adapter.rb +14 -11
  11. data/lib/riddle/client/filter.rb +53 -0
  12. data/lib/riddle/client/message.rb +65 -0
  13. data/lib/riddle/client/response.rb +84 -0
  14. data/lib/riddle/client.rb +619 -0
  15. data/lib/riddle.rb +28 -0
  16. data/test/files/model.rb +23 -0
  17. data/test/files/mysql5.sphinx.conf +97 -0
  18. data/test/files/mysql5.sql +26 -0
  19. data/test/helper.rb +51 -0
  20. data/test/test_adapter.rb +74 -28
  21. data/test/test_attribute.rb +36 -0
  22. data/test/test_index.rb +30 -0
  23. data/test/test_query.rb +47 -32
  24. data/test/test_resource.rb +17 -0
  25. metadata +18 -40
  26. data/lib/dm-sphinx-adapter/client.rb +0 -84
  27. data/lib/dm-sphinx-adapter/config.rb +0 -74
  28. data/lib/dm-sphinx-adapter/config_parser.rb +0 -67
  29. data/test/files/dm_sphinx_adapter_test.sql +0 -21
  30. data/test/files/resource_explicit.rb +0 -25
  31. data/test/files/resource_resource.rb +0 -19
  32. data/test/files/resource_searchable.rb +0 -16
  33. data/test/files/resource_storage_name.rb +0 -11
  34. data/test/files/resource_vanilla.rb +0 -7
  35. data/test/files/sphinx.conf +0 -78
  36. data/test/test_adapter_explicit.rb +0 -48
  37. data/test/test_adapter_resource.rb +0 -25
  38. data/test/test_adapter_searchable.rb +0 -23
  39. data/test/test_adapter_vanilla.rb +0 -46
  40. data/test/test_client.rb +0 -31
  41. data/test/test_config.rb +0 -75
  42. data/test/test_config_parser.rb +0 -29
  43. data/test/test_type_attribute.rb +0 -8
  44. data/test/test_type_index.rb +0 -8
data/Manifest.txt CHANGED
@@ -7,27 +7,20 @@ dm-sphinx-adapter.gemspec
7
7
  lib/dm-sphinx-adapter.rb
8
8
  lib/dm-sphinx-adapter/adapter.rb
9
9
  lib/dm-sphinx-adapter/attribute.rb
10
- lib/dm-sphinx-adapter/client.rb
11
- lib/dm-sphinx-adapter/config.rb
12
- lib/dm-sphinx-adapter/config_parser.rb
13
10
  lib/dm-sphinx-adapter/index.rb
14
11
  lib/dm-sphinx-adapter/query.rb
15
12
  lib/dm-sphinx-adapter/resource.rb
16
- test/files/dm_sphinx_adapter_test.sql
17
- test/files/resource_explicit.rb
18
- test/files/resource_resource.rb
19
- test/files/resource_searchable.rb
20
- test/files/resource_storage_name.rb
21
- test/files/resource_vanilla.rb
22
- test/files/sphinx.conf
13
+ lib/riddle.rb
14
+ lib/riddle/client.rb
15
+ lib/riddle/client/filter.rb
16
+ lib/riddle/client/message.rb
17
+ lib/riddle/client/response.rb
18
+ test/files/model.rb
19
+ test/files/mysql5.sphinx.conf
20
+ test/files/mysql5.sql
21
+ test/helper.rb
23
22
  test/test_adapter.rb
24
- test/test_adapter_explicit.rb
25
- test/test_adapter_resource.rb
26
- test/test_adapter_searchable.rb
27
- test/test_adapter_vanilla.rb
28
- test/test_client.rb
29
- test/test_config.rb
30
- test/test_config_parser.rb
23
+ test/test_attribute.rb
24
+ test/test_index.rb
31
25
  test/test_query.rb
32
- test/test_type_attribute.rb
33
- test/test_type_index.rb
26
+ test/test_resource.rb
data/README.txt CHANGED
@@ -1,6 +1,7 @@
1
1
  = DataMapper Sphinx Adapter
2
2
 
3
- * http://rubyforge.org/projects/dm-sphinx/
3
+ * http://dm-sphinx.rubyforge.org
4
+ * http://rubyforge.org/projects/dm-sphinx
4
5
  * http://github.com/shanna/dm-sphinx-adapter/tree/master
5
6
 
6
7
  == Description
@@ -10,15 +11,13 @@ A DataMapper Sphinx adapter.
10
11
  == Dependencies
11
12
 
12
13
  * dm-core ~> 0.9.7
13
- * riddle ~> 0.9
14
- * daemon_controller ~> 0.2 (optional)
15
14
  * dm-is-searchable ~> 0.9.7 (optional)
16
15
 
17
16
  I'd recommend using the dm-more plugin dm-is-searchable instead of fetching the document id's yourself.
18
17
 
19
18
  == Install
20
19
 
21
- * Via git: git clone git://github.com/shanna/iso-country-codes.git
20
+ * Via git: git clone git://github.com/shanna/dm-sphinx-adapter.git
22
21
  * Via gem: gem install shanna-dm-sphinx-adapter -s http://gems.github.com
23
22
 
24
23
  == Synopsis
@@ -26,9 +25,10 @@ I'd recommend using the dm-more plugin dm-is-searchable instead of fetching the
26
25
  DataMapper uses URIs or a connection has to connect to your data-stores. In this case the sphinx search daemon
27
26
  <tt>searchd</tt>.
28
27
 
29
- On its own this adapter will only return an array of document hashes when queried. The DataMapper library dm-more
30
- however provides dm-is-searchable, a common interface to search one adapter and load documents from another. My
31
- preference is to use this adapter in tandem with dm-is-searchable.
28
+ On its own this adapter will only return an array of document hashes when queried. The DataMapper library
29
+ <tt>dm-is-searchable</tt> however provides a common interface to search one adapter and load documents from another. My
30
+ preference is to use this adapter in tandem with <tt>dm-is-searchable</tt>. See further examples in the synopsis for
31
+ usage with <tt>dm-is-searchable</tt>.
32
32
 
33
33
  Like all DataMapper adapters you can connect with a Hash or URI.
34
34
 
@@ -40,7 +40,6 @@ The breakdown is:
40
40
  - adapter Must be :sphinx
41
41
  - host Hostname (default: localhost)
42
42
  - port Optional port number (default: 3312)
43
- - config Optional but strongly recommended path to sphinx config file.
44
43
 
45
44
  Alternatively supply a Hash:
46
45
  DataMapper.setup(:search, {
@@ -48,7 +47,6 @@ Alternatively supply a Hash:
48
47
  :config => './sphinx.conf' # optional. Recommended though.
49
48
  :host => 'localhost', # optional. Default: localhost
50
49
  :port => 3312 # optional. Default: 3312
51
- :managed => true # optional. Self managed searchd server using daemon_controller.
52
50
  }
53
51
 
54
52
  === DataMapper
@@ -70,7 +68,21 @@ Alternatively supply a Hash:
70
68
  ids = docs.map{|doc| doc[:id]}
71
69
  items = Item.all(:id => ids) # Search :default for all the document id's returned by sphinx.
72
70
 
73
- === DataMapper and Is Searchable
71
+ === DataMapper and IsSearchable
72
+
73
+ IsSearchable is a DataMapper plugin that provides a common search interface when searching from one adapter and reading
74
+ documents from another.
75
+
76
+ IsSearchable will read resources from your <tt>:default</tt> repository on behalf of a search adapter such as
77
+ <tt>dm-sphinx-adapter</tt> and <tt>dm-ferret-adapter</tt>. This saves some of the grunt work (as shown in the previous
78
+ example) by mapping the resulting document id's from a search with your <tt>:search</tt> adapter into a suitable
79
+ <tt>#first</tt> or <tt>#all</tt> query for your <tt>:default</tt> repository.
80
+
81
+ IsSearchable adds a single class method to your resource. The first argument is a <tt>Hash</tt> of
82
+ <tt>DataMapper::Query</tt> conditions to pass to your search adapter (in this case <tt>dm-sphinx-adapter</tt>). An
83
+ optional second <tt>Hash</tt> of <tt>DataMapper::Query</tt> conditions can also be passed and will be appended to the
84
+ query on your <tt>:default</tt> database. This can be handy if you need to add extra exclusions that aren't possible
85
+ using <tt>dm-sphinx-adapter</tt> such as <tt>#gt</tt> or <tt>#lt</tt> conditions.
74
86
 
75
87
  require 'rubygems'
76
88
  require 'dm-core'
@@ -93,7 +105,7 @@ Alternatively supply a Hash:
93
105
  # Fire up your sphinx search daemon and start searching.
94
106
  items = Item.search(:name => 'barney') # Search 'items' index for '@name barney'
95
107
 
96
- === Merb, DataMapper and Is Searchable
108
+ === Merb, DataMapper and IsSearchable
97
109
 
98
110
  # config/init.rb
99
111
  dependency 'dm-is-searchable'
@@ -121,13 +133,12 @@ Alternatively supply a Hash:
121
133
  # Fire up your sphinx search daemon and start searching.
122
134
  Item.search(:name => 'barney') # Search 'items' index for '@name barney'
123
135
 
124
- === DataMapper::SphinxResource
136
+ === DataMapper, IsSearchable and DataMapper::SphinxResource
125
137
 
126
138
  For finer grained control you can include DataMapper::SphinxResource. For instance you can search one or more indexes
127
139
  and sort, include or exclude by attributes defined in your sphinx configuration:
128
140
 
129
141
  class Item
130
- include DataMapper::Resource # Optional, included by SphinxResource if you leave it out yourself.
131
142
  include DataMapper::SphinxResource
132
143
  property :id, Serial
133
144
  property :name, String
@@ -148,28 +159,16 @@ and sort, include or exclude by attributes defined in your sphinx configuration:
148
159
 
149
160
  == Sphinx Configuration
150
161
 
151
- Though you don't have to supply the sphinx configuration file to dm-sphinx-adapter I'd recommend doing it anyway.
152
- It's more DRY since all searchd/indexer options can be read straight from the configuration.
153
-
154
- DataMapper.setup(:search, :adapter => 'sphinx', :config => '/path/to/sphinx.conf')
155
- DataMapper.setup(:search, 'sphinx://localhost/path/to/sphinx.conf')
156
-
157
- If your sphinx.conf lives in either of the default locations /usr/local/etc/sphinx.conf or ./sphinx.conf then you
158
- only need to supply:
159
-
160
- DataMapper.setup(:search, :adapter => 'sphinx')
162
+ No limitations, restrictions or requirement are imposed on your sphinx configuration. The adapter will not generate nor
163
+ overwrite your finely crafted config file.
161
164
 
162
165
  == Searchd
163
166
 
164
- As of 0.2 I've added a managed searchd option using daemon_controller. It may come in handy if you only use God, Monit
165
- or whatever in production. Use the Hash form of DataMapper#setup and supply the option :managed with a true value and
166
- daemon_controller will start searchd on demand.
167
-
168
- It is already strongly encouraged but you will need to specify the path to your sphinx configuration file in order for
169
- searchd to run. See Sphinx Configuration, DataMapper::Adapters::Sphinx::ManagedClient.
167
+ To keep things simple, this adapter does not manage your sphinx server. Try one of these fine offerings:
170
168
 
171
- The daemon_controller library can be found only on github, not rubyforge.
172
- See http://github.com/FooBarWidget/daemon_controller/tree/master
169
+ * god[http://god.rubyforge.org]
170
+ * daemon_controller[http://github.com/FooBarWidget/daemon_controller/tree/master]
171
+ * monit[http://www.tildeslash.com/monit]
173
172
 
174
173
  == Indexer and Live(ish) updates.
175
174
 
@@ -182,13 +181,6 @@ For reliable live(ish) updates in a main + delta scheme it's probably best you s
182
181
  Andrew (Shodan) Aksyonoff of Sphinx suggests a cronjob or alternatively if you need even less lag to "run indexer in
183
182
  an endless loop, with a few seconds of sleep in between to allow searchd some headroom to pick up the changes".
184
183
 
185
- == Todo
186
-
187
- * Loads of documentation. Most of it is unchecked YARD at the moment.
188
- * Add DataMapper::Adapters::Sphinx::Client#attribute_set to allow attribute modification on one or more indexes. It's
189
- the only thing missing if you understand the pitfalls and still want to add thinking-sphinx like delta indexing to
190
- your resource.
191
-
192
184
  == Contributing
193
185
 
194
186
  Go nuts. Just send me a pull request (github or otherwise) when you are happy with your code.
data/Rakefile CHANGED
@@ -3,11 +3,10 @@
3
3
  require 'rubygems'
4
4
  require 'hoe'
5
5
 
6
- Hoe.new('dm-sphinx-adapter', '0.5') do |p|
6
+ Hoe.new('dm-sphinx-adapter', '0.6') do |p|
7
7
  p.developer('Shane Hanna', 'shane.hanna@gmail.com')
8
8
  p.extra_deps = [
9
- ['dm-core', '~> 0.9.7'],
10
- ['riddle', '~> 0.9']
9
+ ['dm-core', '~> 0.9.7']
11
10
  ]
12
11
  end
13
12
 
@@ -2,23 +2,23 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{dm-sphinx-adapter}
5
- s.version = "0.5"
5
+ s.version = "0.6"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["Shane Hanna"]
9
- s.date = %q{2008-12-01}
9
+ s.date = %q{2008-12-13}
10
10
  s.description = %q{A DataMapper Sphinx adapter.}
11
11
  s.email = ["shane.hanna@gmail.com"]
12
12
  s.extra_rdoc_files = ["History.txt", "LICENCE.txt", "Manifest.txt", "README.txt"]
13
- s.files = ["History.txt", "LICENCE.txt", "Manifest.txt", "README.txt", "Rakefile", "dm-sphinx-adapter.gemspec", "lib/dm-sphinx-adapter.rb", "lib/dm-sphinx-adapter/adapter.rb", "lib/dm-sphinx-adapter/attribute.rb", "lib/dm-sphinx-adapter/client.rb", "lib/dm-sphinx-adapter/config.rb", "lib/dm-sphinx-adapter/config_parser.rb", "lib/dm-sphinx-adapter/index.rb", "lib/dm-sphinx-adapter/query.rb", "lib/dm-sphinx-adapter/resource.rb", "test/files/dm_sphinx_adapter_test.sql", "test/files/resource_explicit.rb", "test/files/resource_resource.rb", "test/files/resource_searchable.rb", "test/files/resource_storage_name.rb", "test/files/resource_vanilla.rb", "test/files/sphinx.conf", "test/test_adapter.rb", "test/test_adapter_explicit.rb", "test/test_adapter_resource.rb", "test/test_adapter_searchable.rb", "test/test_adapter_vanilla.rb", "test/test_client.rb", "test/test_config.rb", "test/test_config_parser.rb", "test/test_query.rb", "test/test_type_attribute.rb", "test/test_type_index.rb"]
13
+ s.files = ["History.txt", "LICENCE.txt", "Manifest.txt", "README.txt", "Rakefile", "dm-sphinx-adapter.gemspec", "lib/dm-sphinx-adapter.rb", "lib/dm-sphinx-adapter/adapter.rb", "lib/dm-sphinx-adapter/attribute.rb", "lib/dm-sphinx-adapter/index.rb", "lib/dm-sphinx-adapter/query.rb", "lib/dm-sphinx-adapter/resource.rb", "lib/riddle.rb", "lib/riddle/client.rb", "lib/riddle/client/filter.rb", "lib/riddle/client/message.rb", "lib/riddle/client/response.rb", "test/files/model.rb", "test/files/mysql5.sphinx.conf", "test/files/mysql5.sql", "test/helper.rb", "test/test_adapter.rb", "test/test_attribute.rb", "test/test_index.rb", "test/test_query.rb", "test/test_resource.rb"]
14
14
  s.has_rdoc = true
15
- s.homepage = %q{http://rubyforge.org/projects/dm-sphinx/}
15
+ s.homepage = %q{http://dm-sphinx.rubyforge.org}
16
16
  s.rdoc_options = ["--main", "README.txt"]
17
17
  s.require_paths = ["lib"]
18
18
  s.rubyforge_project = %q{dm-sphinx-adapter}
19
- s.rubygems_version = %q{1.3.0}
19
+ s.rubygems_version = %q{1.3.1}
20
20
  s.summary = %q{A DataMapper Sphinx adapter.}
21
- s.test_files = ["test/test_adapter.rb", "test/test_adapter_explicit.rb", "test/test_adapter_resource.rb", "test/test_adapter_searchable.rb", "test/test_adapter_vanilla.rb", "test/test_client.rb", "test/test_config.rb", "test/test_config_parser.rb", "test/test_query.rb", "test/test_type_attribute.rb", "test/test_type_index.rb"]
21
+ s.test_files = ["test/test_adapter.rb", "test/test_attribute.rb", "test/test_index.rb", "test/test_query.rb", "test/test_resource.rb"]
22
22
 
23
23
  if s.respond_to? :specification_version then
24
24
  current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
@@ -26,16 +26,13 @@ Gem::Specification.new do |s|
26
26
 
27
27
  if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
28
28
  s.add_runtime_dependency(%q<dm-core>, ["~> 0.9.7"])
29
- s.add_runtime_dependency(%q<riddle>, ["~> 0.9"])
30
29
  s.add_development_dependency(%q<hoe>, [">= 1.8.2"])
31
30
  else
32
31
  s.add_dependency(%q<dm-core>, ["~> 0.9.7"])
33
- s.add_dependency(%q<riddle>, ["~> 0.9"])
34
32
  s.add_dependency(%q<hoe>, [">= 1.8.2"])
35
33
  end
36
34
  else
37
35
  s.add_dependency(%q<dm-core>, ["~> 0.9.7"])
38
- s.add_dependency(%q<riddle>, ["~> 0.9"])
39
36
  s.add_dependency(%q<hoe>, [">= 1.8.2"])
40
37
  end
41
38
  end
@@ -1,7 +1,5 @@
1
1
  require 'benchmark'
2
2
 
3
- # TODO: I think perhaps I should move all the query building code to a lib of its own.
4
-
5
3
  module DataMapper
6
4
  module Adapters
7
5
  module Sphinx
@@ -24,60 +22,83 @@ module DataMapper
24
22
  # - adapter Must be :sphinx
25
23
  # - host Hostname (default: localhost)
26
24
  # - port Optional port number (default: 3312)
27
- # - config Optional but recommended path to sphinx config file.
28
25
  #
29
26
  # Alternatively supply a Hash:
30
27
  # DataMapper.setup(:search, {
31
28
  # :adapter => 'sphinx', # required
32
- # :config => './sphinx.conf' # optional. Recommended though.
33
29
  # :host => 'localhost', # optional. Default: localhost
34
30
  # :port => 3312 # optional. Default: 3312
35
- # :managed => true # optional. Self managed searchd server using daemon_controller.
36
31
  # })
37
32
  class Adapter < AbstractAdapter
38
- ##
39
- # Initialize the sphinx adapter.
33
+
34
+ # ==== See
35
+ # * DataMapper::Adapters::AbstractAdapter
40
36
  #
41
- # @param [URI, DataObject::URI, Addressable::URI, String, Hash, Pathname] uri_or_options
42
- # @see DataMapper::Adapters::Sphinx::Config
43
- # @see DataMapper::Adapters::Sphinx::Client
37
+ # ==== Parameters
38
+ # uri_or_options<URI, DataObject::URI, Addressable::URI, String, Hash, Pathname>::
39
+ # DataMapper uri or options hash.
44
40
  def initialize(name, uri_or_options)
45
- super
46
-
47
- managed = !!(uri_or_options.kind_of?(Hash) && uri_or_options[:managed])
48
- @client = managed ? ManagedClient.new(uri_or_options) : Client.new(uri_or_options)
41
+ options = normalize_options(uri_or_options)
42
+ @client = Riddle::Client.new(options.delete(:host), options.delete(:port))
43
+ options.each{|k, v| @client.method("#{k}=".to_sym).call(v) if @client.respond_to?("#{k}=".to_sym)}
49
44
  end
50
45
 
51
- ##
52
- # Interaction with searchd and indexer.
53
- #
54
- # @see DataMapper::Adapters::Sphinx::Client
55
- # @see DataMapper::Adapters::Sphinx::ManagedClient
56
- attr_reader :client
57
-
58
46
  def create(resources) #:nodoc:
59
- true
47
+ 0
60
48
  end
61
49
 
62
50
  def delete(query) #:nodoc:
63
- true
51
+ 0
64
52
  end
65
53
 
54
+ # Query your Sphinx repository and return all matching documents.
55
+ #
56
+ # ==== Notes
57
+ #
58
+ # These methods are public but normally called indirectly through DataMapper::Resource#get,
59
+ # DataMapper::Resource#first or DataMapper::Resource#all.
60
+ #
61
+ # ==== Parameters
62
+ # query<DataMapper::Query>:: The query object.
63
+ #
64
+ # ==== Returns
65
+ # Array<Hash>:: An array of document hashes. <tt>[{:id => 1}, {:id => 2}]</tt>
66
+ # Array<>:: An empty array if no documents match.
66
67
  def read_many(query)
67
68
  read(query)
68
69
  end
69
70
 
71
+ # Query your Sphinx repository and return the first document matched.
72
+ #
73
+ # ==== Notes
74
+ #
75
+ # These methods are public but normally called indirectly through DataMapper::Resource#get,
76
+ # DataMapper::Resource#first or DataMapper::Resource#all.
77
+ #
78
+ # ==== Parameters
79
+ # query<DataMapper::Query>:: The query object.
80
+ #
81
+ # ==== Returns
82
+ # Hash:: An document hash of the first document matched. <tt>{:id => 1}</tt>
83
+ # Nil:: If no documents match.
70
84
  def read_one(query)
71
85
  read(query).first
72
86
  end
73
87
 
74
88
  protected
75
- ##
76
89
  # List sphinx indexes to search.
90
+ #
77
91
  # If no indexes are explicitly declared using DataMapper::Adapters::Sphinx::Resource then the default storage
78
92
  # name is used.
79
93
  #
80
- # @see DataMapper::Adapters::Sphinx::Resource#sphinx_indexes
94
+ # ==== See
95
+ # * DataMapper::Adapters::Sphinx::Resource::ClassMethods#sphinx_indexes
96
+ #
97
+ # ==== Parameters
98
+ # model<DataMapper::Model>:: The DataMapper::Model.
99
+ #
100
+ # ==== Returns
101
+ # Array<DataMapper::Adapters::Sphinx::Index>:: Index objects from the model.
81
102
  def indexes(model)
82
103
  indexes = model.sphinx_indexes(repository(self.name).name) if model.respond_to?(:sphinx_indexes)
83
104
  if indexes.nil? or indexes.empty?
@@ -86,71 +107,57 @@ module DataMapper
86
107
  indexes
87
108
  end
88
109
 
89
- ##
90
- # List sphinx delta indexes to search.
91
- #
92
- # @see DataMapper::Adapters::Sphinx::Resource#sphinx_indexes
93
- def delta_indexes(model)
94
- indexes(model).find_all{|i| i.delta?}
95
- end
96
-
97
- ##
98
110
  # Query sphinx for a list of document IDs.
99
111
  #
100
- # @param [DataMapper::Query]
112
+ # ==== Parameters
113
+ # query<DataMapper::Query>:: The query object.
114
+ #
115
+ # ==== Returns
116
+ # Array<Hash>:: An array of document hashes. <tt>[{:id => 1}, {:id => 2}]</tt>
117
+ # Array<>:: An empty array if no documents match.
101
118
  def read(query)
102
119
  from = indexes(query.model).map{|index| index.name}.join(', ')
103
120
  search = Sphinx::Query.new(query).to_s
104
- options = {
105
- :match_mode => :extended, # TODO: Modes!
106
- :filters => search_filters(query) # By attribute.
107
- }
108
- options[:limit] = query.limit.to_i if query.limit
109
- options[:offset] = query.offset.to_i if query.offset
121
+
122
+ client = @client.dup
123
+ client.match_mode = :extended
124
+ client.filters = search_filters(query) # By attribute.
125
+ client.limit = query.limit.to_i if query.limit
126
+ client.offset = query.offset.to_i if query.offset
110
127
 
111
128
  if order = search_order(query)
112
- options.update(
113
- :sort_mode => :extended,
114
- :sort_by => order
115
- )
129
+ client.sort_mode = :extended
130
+ client.sort_by = order
116
131
  end
117
132
 
118
- res = @client.search(search, from, options)
119
- raise res[:error] unless res[:error].nil?
133
+ result = client.query(search, from)
134
+ raise result[:error] unless result[:error].nil?
120
135
 
121
136
  DataMapper.logger.info(
122
- %q{Sphinx (%.3f): search '%s' in '%s' found %d documents} % [res[:time], search, from, res[:total]]
137
+ %q{Sphinx (%.3f): search '%s' in '%s' found %d documents} % [result[:time], search, from, result[:total]]
123
138
  )
124
- res[:matches].map{|doc| {:id => doc[:doc]}}
139
+ result[:matches].map{|doc| {:id => doc[:doc]}}
125
140
  end
126
141
 
127
142
 
128
- ##
129
- # Sphinx search query filters from attributes.
130
- # @param [DataMapper::Query]
131
- # @return [Array]
132
- def search_filters(query)
143
+ # Riddle search filters for attributes.
144
+ def search_filters(query) #:nodoc:
133
145
  filters = []
134
146
  query.conditions.each do |operator, attribute, value|
135
147
  next unless attribute.kind_of? Sphinx::Attribute
136
- # TODO: Value cast to uint, bool, str2ordinal, float
137
148
  filters << case operator
138
- when :eql, :like then Riddle::Client::Filter.new(attribute.name.to_s, filter_value(value))
139
- when :not then Riddle::Client::Filter.new(attribute.name.to_s, filter_value(value), true)
149
+ when :eql, :like then attribute.filter(value)
150
+ when :not then attribute.filter(value, false)
140
151
  else raise NotImplementedError.new("Sphinx: Query attributes do not support the #{operator} operator")
141
152
  end
142
153
  end
143
154
  filters
144
155
  end
145
156
 
146
- ##
147
- # Order by attributes.
148
- #
149
- # @return [String or Symbol]
150
- def search_order(query)
157
+ # TODO: How do you tell the difference between the default query order and someone explicitly asking for
158
+ # sorting by the primary key? I don't think you can at the moment.
159
+ def search_order(query) #:nodoc:
151
160
  by = []
152
- # TODO: How do you tell the difference between the default query order and someone explicitly asking for
153
- # sorting by the primary key?
154
161
  query.order.each do |order|
155
162
  next unless order.property.kind_of? Sphinx::Attribute
156
163
  by << [order.property.field, order.direction].join(' ')
@@ -158,18 +165,25 @@ module DataMapper
158
165
  by.empty? ? nil : by.join(', ')
159
166
  end
160
167
 
161
- # TODO: Move this to Attribute#dump.
162
- # This is ninja'd straight from TS just to get things going.
163
- def filter_value(value)
164
- case value
165
- when Range
166
- value.first.is_a?(Time) ? value.first.to_i..value.last.to_i : value
167
- when Array
168
- value.collect { |val| val.is_a?(Time) ? val.to_i : val }
168
+ # Coerce +uri_or_options+ into a +Hash+ of options.
169
+ #
170
+ # ==== Parameters
171
+ # uri_or_options<URI, DataObject::URI, Addressable::URI, String, Hash, Pathname>::
172
+ # DataMapper uri or options hash.
173
+ #
174
+ # ==== Returns
175
+ # Hash
176
+ def normalize_options(uri_or_options)
177
+ case uri_or_options
178
+ when String, Addressable::URI then DataObjects::URI.parse(uri_or_options).attributes
179
+ when DataObjects::URI then uri_or_options.attributes
180
+ when Pathname then {:path => uri_or_options}
169
181
  else
170
- Array(value)
182
+ uri_or_options[:path] ||= uri_or_options.delete(:config) || uri_or_options.delete(:database)
183
+ uri_or_options
171
184
  end
172
185
  end
186
+
173
187
  end # Adapter
174
188
  end # Sphinx
175
189
 
@@ -1,29 +1,38 @@
1
+ require 'date'
2
+ require 'time'
3
+
1
4
  module DataMapper
2
5
  module Adapters
3
6
  module Sphinx
4
7
 
5
- ##
6
- # Define a Sphinx attribute.
8
+ # Sphinx attribute definition.
9
+ #
10
+ # You must declare attributes as such if you want to use them for sorting or conditions.
7
11
  #
8
- # Supports only a subset of DataMapper::Property types that can be used as Sphinx attributes.
12
+ # ==== Notes
13
+ # The following primatives will be used as sql_attr_* types. Some liberty has been taken to accommodate for as
14
+ # many DM primitives as possible.
9
15
  #
10
- # * TrueClass # sql_attr_bool
11
- # * String # sql_attr_str2ordinal
12
- # * Float, # sql_attr_float
13
- # * Integer, # sql_attr_uint
14
- # * DateTime, # sql_attr_timestamp
15
- # * Date, # sql_attr_timestamp
16
- # * DataMapper::Types::Serial # sql_attr_uint
16
+ # TrueClass:: sql_attr_bool
17
+ # String:: sql_attr_str2ordinal
18
+ # DataMapper::Types::Text:: sql_attr_str2ordinal
19
+ # Float:: sql_attr_float
20
+ # Integer:: sql_attr_uint
21
+ # BigDecimal:: sql_attr_float
22
+ # DateTime:: sql_attr_timestamp
23
+ # Date:: sql_attr_timestamp
24
+ # Time:: sql_attr_timestamp
25
+ # DataMapper::Types::Serial:: sql_attr_uint
17
26
  class Attribute < Property
18
27
 
19
28
  # DataMapper types supported as Sphinx attributes.
20
29
  TYPES = [
21
30
  TrueClass, # sql_attr_bool
22
31
  String, # sql_attr_str2ordinal
23
- # DataMapper::Types::Text,
32
+ DataMapper::Types::Text, # sql_attr_str2ordinal
24
33
  Float, # sql_attr_float
25
34
  Integer, # sql_attr_uint
26
- # BigDecimal, # sql_attr_float?
35
+ BigDecimal, # sql_attr_float
27
36
  DateTime, # sql_attr_timestamp
28
37
  Date, # sql_attr_timestamp
29
38
  Time, # sql_attr_timestamp
@@ -33,6 +42,41 @@ module DataMapper
33
42
  DataMapper::Types::Serial # sql_attr_uint
34
43
  ]
35
44
 
45
+ # Create a riddle client filter from a value.
46
+ #
47
+ # ==== Parameters
48
+ # value<Object>::
49
+ # The filter value to typecast and include/exclude.
50
+ #
51
+ # inclusive<Boolean>::
52
+ # Include or exclude results matching the filter value. Default: inclusive (true).
53
+ #
54
+ # ==== Returns
55
+ # Riddle::Client::Filter::
56
+ def filter(value, inclusive = true)
57
+ # Riddle uses exclusive = false as the default which doesn't read well IMO. Nobody says "Yes I don't want
58
+ # these values" you say "No I don't want these values".
59
+ value = typecast(value)
60
+ value = [value] unless value.quacks_like?([Array, Range])
61
+ Riddle::Client::Filter.new(field, value, !inclusive)
62
+ end
63
+
64
+ # Typecasts the value into a sphinx primitive. Supports ranges or arrays of values.
65
+ #
66
+ # ==== Notes
67
+ # Some loss of precision may occur when casting BigDecimal to Float.
68
+ def typecast(value)
69
+ if value.kind_of?(Range) then Range.new(typecast(value.first), typecast(value.last))
70
+ elsif value.kind_of?(Array) then value.map{|v| typecast(v)}
71
+ elsif primitive == BigDecimal then super(value).to_f
72
+ elsif primitive == DateTime then Time.parse(super(value).to_s).to_i
73
+ elsif primitive == Date then Time.parse(super(value).to_s).to_i
74
+ elsif primitive == Time then super(value).to_i
75
+ else
76
+ super(value) # Good luck
77
+ end
78
+ end
79
+
36
80
  end # Attribute
37
81
  end # Sphinx
38
82
  end # Adapters
@@ -1,11 +1,23 @@
1
1
  module DataMapper
2
2
  module Adapters
3
3
  module Sphinx
4
+
5
+ # Sphinx index definition.
4
6
  class Index
5
7
  include Assertions
6
8
 
9
+ # Options.
7
10
  attr_reader :model, :name, :options
8
11
 
12
+ # ==== Parameters
13
+ # model<DataMapper::Model>:: Your resources model.
14
+ # name<Symbol, String>:: The index name.
15
+ # options<Hash>:: Optional arguments.
16
+ #
17
+ # ==== Options
18
+ # :delta<Boolean>::
19
+ # Delta index. Delta indexes will be searched last when multiple indexes are defined for a
20
+ # resource. Default is false.
9
21
  def initialize(model, name, options = {})
10
22
  assert_kind_of 'model', model, Model
11
23
  assert_kind_of 'name', name, Symbol, String
@@ -13,9 +25,10 @@ module DataMapper
13
25
 
14
26
  @model = model
15
27
  @name = name.to_sym
16
- @delta = options.fetch(:delta, nil)
28
+ @delta = options.fetch(:delta, false)
17
29
  end
18
30
 
31
+ # Is the index a delta index.
19
32
  def delta?
20
33
  !!@delta
21
34
  end