shanna-dm-sphinx-adapter 0.4

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 ADDED
@@ -0,0 +1,17 @@
1
+ === 0.4 / 2008-11-21
2
+
3
+ * Fixed broken dm-is-searchable support.
4
+ * Bumped version because the read_one/read_many result structure had to change to support dm-is-searchable.
5
+
6
+ === 0.3 / 2008-11-18
7
+
8
+ * Removed calls to indexer on create/update. See README.txt
9
+ * Made the client object available from the adapter.
10
+
11
+ === 0.2 / 2008-11-09
12
+
13
+ * Addributes.
14
+ * Self managed searchd daemon if you want it.
15
+
16
+ === 0.1 / 2008-10-24
17
+
data/LICENCE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Shane Hanna
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Manifest.txt ADDED
@@ -0,0 +1,31 @@
1
+ History.txt
2
+ LICENCE.txt
3
+ Manifest.txt
4
+ README.txt
5
+ Rakefile
6
+ dm-sphinx-adapter.gemspec
7
+ lib/dm-sphinx-adapter.rb
8
+ lib/dm-sphinx-adapter/adapter.rb
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
+ lib/dm-sphinx-adapter/index.rb
14
+ lib/dm-sphinx-adapter/resource.rb
15
+ test/files/dm_sphinx_adapter_test.sql
16
+ test/files/resource_explicit.rb
17
+ test/files/resource_resource.rb
18
+ test/files/resource_searchable.rb
19
+ test/files/resource_storage_name.rb
20
+ test/files/resource_vanilla.rb
21
+ test/files/sphinx.conf
22
+ test/test_adapter.rb
23
+ test/test_adapter_explicit.rb
24
+ test/test_adapter_resource.rb
25
+ test/test_adapter_searchable.rb
26
+ test/test_adapter_vanilla.rb
27
+ test/test_client.rb
28
+ test/test_config.rb
29
+ test/test_config_parser.rb
30
+ test/test_type_attribute.rb
31
+ test/test_type_index.rb
data/README.txt ADDED
@@ -0,0 +1,195 @@
1
+ = DataMapper Sphinx Adapter
2
+
3
+ * http://rubyforge.org/projects/dm-sphinx/
4
+ * http://github.com/shanna/dm-sphinx-adapter/tree/master
5
+
6
+ == Description
7
+
8
+ A DataMapper Sphinx adapter.
9
+
10
+ == Dependencies
11
+
12
+ * dm-core ~> 0.9.7
13
+ * riddle ~> 0.9
14
+ * daemon_controller ~> 0.2 (optional)
15
+ * dm-is-searchable ~> 0.9.7 (optional)
16
+
17
+ I'd recommend using the dm-more plugin dm-is-searchable instead of fetching the document id's yourself.
18
+
19
+ == Install
20
+
21
+ * Via git: git clone git://github.com/shanna/iso-country-codes.git
22
+ * Via gem: gem install shanna-dm-sphinx-adapter -s http://gems.github.com
23
+
24
+ == Synopsis
25
+
26
+ DataMapper uses URIs or a connection has to connect to your data-stores. In this case the sphinx search daemon
27
+ <tt>searchd</tt>.
28
+
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.
32
+
33
+ Like all DataMapper adapters you can connect with a Hash or URI.
34
+
35
+ A URI:
36
+ DataMapper.setup(:search, 'sphinx://localhost')
37
+
38
+ The breakdown is:
39
+ "#{adapter}://#{host}:#{port}/#{config}"
40
+ - adapter Must be :sphinx
41
+ - host Hostname (default: localhost)
42
+ - port Optional port number (default: 3312)
43
+ - config Optional but strongly recommended path to sphinx config file.
44
+
45
+ Alternatively supply a Hash:
46
+ DataMapper.setup(:search, {
47
+ :adapter => 'sphinx', # required
48
+ :config => './sphinx.conf' # optional. Recommended though.
49
+ :host => 'localhost', # optional. Default: localhost
50
+ :port => 3312 # optional. Default: 3312
51
+ :managed => true # optional. Self managed searchd server using daemon_controller.
52
+ }
53
+
54
+ === DataMapper
55
+
56
+ require 'rubygems'
57
+ require 'dm-sphinx-adapter'
58
+
59
+ DataMapper.setup(:default, 'sqlite3::memory:')
60
+ DataMapper.setup(:search, 'sphinx://localhost:3312')
61
+
62
+ class Item
63
+ include DataMapper::Resource
64
+ property :id, Serial
65
+ property :name, String
66
+ end
67
+
68
+ # Fire up your sphinx search daemon and start searching.
69
+ docs = repository(:search){ Item.all(:name => 'barney') } # Search 'items' index for '@name barney'
70
+ ids = docs.map{|doc| doc[:id]}
71
+ items = Item.all(:id => ids) # Search :default for all the document id's returned by sphinx.
72
+
73
+ === DataMapper and Is Searchable
74
+
75
+ require 'rubygems'
76
+ require 'dm-core'
77
+ require 'dm-is-searchable'
78
+ require 'dm-sphinx-adapter'
79
+
80
+ # Connections.
81
+ DataMapper.setup(:default, 'sqlite3::memory:')
82
+ DataMapper.setup(:search, 'sphinx://localhost:3312')
83
+
84
+ class Item
85
+ include DataMapper::Resource
86
+ property :id, Serial
87
+ property :name, String
88
+
89
+ is :searchable # defaults to :search repository though you can be explicit:
90
+ # is :searchable, :repository => :sphinx
91
+ end
92
+
93
+ # Fire up your sphinx search daemon and start searching.
94
+ items = Item.search(:name => 'barney') # Search 'items' index for '@name barney'
95
+
96
+ === Merb, DataMapper and Is Searchable
97
+
98
+ # config/init.rb
99
+ dependency 'dm-is-searchable'
100
+ dependency 'dm-sphinx-adapter'
101
+
102
+ # config/database.yml
103
+ ---
104
+ development: &defaults
105
+ repositories:
106
+ search:
107
+ adapter: sphinx
108
+ host: localhost
109
+ port: 3312
110
+
111
+ # app/models/item.rb
112
+ class Item
113
+ include DataMapper::Resource
114
+ property :id, Serial
115
+ property :name, String
116
+
117
+ is :searchable # defaults to :search repository though you can be explicit:
118
+ # is :searchable, :repository => :sphinx
119
+ end # Item
120
+
121
+ # Fire up your sphinx search daemon and start searching.
122
+ Item.search(:name => 'barney') # Search 'items' index for '@name barney'
123
+
124
+ === DataMapper::SphinxResource
125
+
126
+ For finer grained control you can include DataMapper::SphinxResource. For instance you can search one or more indexes
127
+ and sort, include or exclude by attributes defined in your sphinx configuration:
128
+
129
+ class Item
130
+ include DataMapper::Resource # Optional, included by SphinxResource if you leave it out yourself.
131
+ include DataMapper::SphinxResource
132
+ property :id, Serial
133
+ property :name, String
134
+
135
+ is :searchable
136
+ repository(:search) do
137
+ index :items
138
+ index :items_delta, :delta => true
139
+
140
+ # Sphinx attributes to sort include/exclude by.
141
+ attribute :updated_on, DateTime
142
+ end
143
+
144
+ end # Item
145
+
146
+ # Search 'items, items_delta' index for '@name barney' updated in the last 30 minutes.
147
+ Item.search(:name => 'barney', :updated => (Time.now - 1800 .. Time.now))
148
+
149
+ == Sphinx Configuration
150
+
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')
161
+
162
+ == Searchd
163
+
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.
170
+
171
+ The daemon_controller library can be found only on github, not rubyforge.
172
+ See http://github.com/FooBarWidget/daemon_controller/tree/master
173
+
174
+ == Indexer and Live(ish) updates.
175
+
176
+ As of 0.3 the indexer will no longer be fired on create/update even if you have delta indexes defined. Sphinx indexing
177
+ is blazing fast but unless your resource sees very little activity you will run the risk of lock errors on
178
+ the temporary delta index files (.tmpl.sp1) and your delta index won't be updated. Given this functionality is
179
+ unreliable at best I've chosen to remove it.
180
+
181
+ For reliable live(ish) updates in a main + delta scheme it's probably best you schedule them outside of your ORM.
182
+ Andrew (Shodan) Aksyonoff of Sphinx suggests a cronjob or alternatively if you need even less lag to "run indexer in
183
+ an endless loop, with a few seconds of sleep in between to allow searchd some headroom to pick up the changes".
184
+
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
+ == Contributing
193
+
194
+ Go nuts. Just send me a pull request (github or otherwise) when you are happy with your code.
195
+
data/Rakefile ADDED
@@ -0,0 +1,23 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'rubygems'
4
+ require 'hoe'
5
+
6
+ Hoe.new('dm-sphinx-adapter', '0.4') do |p|
7
+ p.developer('Shane Hanna', 'shane.hanna@gmail.com')
8
+ p.extra_deps = [
9
+ ['dm-core', '~> 0.9.7'],
10
+ ['riddle', '~> 0.9']
11
+ ]
12
+ end
13
+
14
+ # http://blog.behindlogic.com/2008/10/auto-generate-your-manifest-and-gemspec.html
15
+ desc 'Rebuild manifest and gemspec.'
16
+ task :cultivate do
17
+ Dir.chdir(File.dirname(__FILE__)) do #TODO: Is this required?
18
+ system %q{git ls-files | grep -v "\.gitignore" > Manifest.txt}
19
+ system %q{rake debug_gem | grep -v "(in " > `basename \`pwd\``.gemspec}
20
+ end
21
+ end
22
+
23
+ # vim: syntax=Ruby
@@ -0,0 +1,41 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{dm-sphinx-adapter}
5
+ s.version = "0.4"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Shane Hanna"]
9
+ s.date = %q{2008-11-24}
10
+ s.description = %q{A DataMapper Sphinx adapter.}
11
+ s.email = ["shane.hanna@gmail.com"]
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/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_type_attribute.rb", "test/test_type_index.rb"]
14
+ s.has_rdoc = true
15
+ s.homepage = %q{http://rubyforge.org/projects/dm-sphinx/}
16
+ s.rdoc_options = ["--main", "README.txt"]
17
+ s.require_paths = ["lib"]
18
+ s.rubyforge_project = %q{dm-sphinx-adapter}
19
+ s.rubygems_version = %q{1.3.0}
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_type_attribute.rb", "test/test_type_index.rb"]
22
+
23
+ if s.respond_to? :specification_version then
24
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
25
+ s.specification_version = 2
26
+
27
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
28
+ s.add_runtime_dependency(%q<dm-core>, ["~> 0.9.7"])
29
+ s.add_runtime_dependency(%q<riddle>, ["~> 0.9"])
30
+ s.add_development_dependency(%q<hoe>, [">= 1.8.2"])
31
+ else
32
+ s.add_dependency(%q<dm-core>, ["~> 0.9.7"])
33
+ s.add_dependency(%q<riddle>, ["~> 0.9"])
34
+ s.add_dependency(%q<hoe>, [">= 1.8.2"])
35
+ end
36
+ else
37
+ s.add_dependency(%q<dm-core>, ["~> 0.9.7"])
38
+ s.add_dependency(%q<riddle>, ["~> 0.9"])
39
+ s.add_dependency(%q<hoe>, [">= 1.8.2"])
40
+ end
41
+ end
@@ -0,0 +1,218 @@
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
+ module Sphinx
8
+ # == Synopsis
9
+ #
10
+ # DataMapper uses URIs or a connection has to connect to your data-stores. In this case the sphinx search daemon
11
+ # <tt>searchd</tt>.
12
+ #
13
+ # On its own this adapter will only return an array of document hashes when queried. The DataMapper library dm-more
14
+ # however provides dm-is-searchable, a common interface to search one adapter and load documents from another. My
15
+ # preference is to use this adapter in tandem with dm-is-searchable.
16
+ #
17
+ # Like all DataMapper adapters you can connect with a Hash or URI.
18
+ #
19
+ # A URI:
20
+ # DataMapper.setup(:search, 'sphinx://localhost')
21
+ #
22
+ # The breakdown is:
23
+ # "#{adapter}://#{host}:#{port}/#{config}"
24
+ # - adapter Must be :sphinx
25
+ # - host Hostname (default: localhost)
26
+ # - port Optional port number (default: 3312)
27
+ # - config Optional but recommended path to sphinx config file.
28
+ #
29
+ # Alternatively supply a Hash:
30
+ # DataMapper.setup(:search, {
31
+ # :adapter => 'sphinx', # required
32
+ # :config => './sphinx.conf' # optional. Recommended though.
33
+ # :host => 'localhost', # optional. Default: localhost
34
+ # :port => 3312 # optional. Default: 3312
35
+ # :managed => true # optional. Self managed searchd server using daemon_controller.
36
+ # })
37
+ class Adapter < AbstractAdapter
38
+ ##
39
+ # Initialize the sphinx adapter.
40
+ #
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
44
+ 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)
49
+ end
50
+
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
+ def create(resources) #:nodoc:
59
+ true
60
+ end
61
+
62
+ def delete(query) #:nodoc:
63
+ true
64
+ end
65
+
66
+ def read_many(query)
67
+ read(query)
68
+ end
69
+
70
+ def read_one(query)
71
+ read(query).first
72
+ end
73
+
74
+ protected
75
+ ##
76
+ # List sphinx indexes to search.
77
+ # If no indexes are explicitly declared using DataMapper::Adapters::Sphinx::Resource then the default storage
78
+ # name is used.
79
+ #
80
+ # @see DataMapper::Adapters::Sphinx::Resource#sphinx_indexes
81
+ def indexes(model)
82
+ indexes = model.sphinx_indexes(repository(self.name).name) if model.respond_to?(:sphinx_indexes)
83
+ if indexes.nil? or indexes.empty?
84
+ indexes = [Index.new(model, model.storage_name)]
85
+ end
86
+ indexes
87
+ end
88
+
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
+ # Query sphinx for a list of document IDs.
99
+ #
100
+ # @param [DataMapper::Query]
101
+ def read(query)
102
+ from = indexes(query.model).map{|index| index.name}.join(', ')
103
+ search = search_query(query)
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
110
+
111
+ if order = search_order(query)
112
+ options.update(
113
+ :sort_mode => :extended,
114
+ :sort_by => order
115
+ )
116
+ end
117
+
118
+ res = @client.search(search, from, options)
119
+ raise res[:error] unless res[:error].nil?
120
+
121
+ DataMapper.logger.info(
122
+ %q{Sphinx (%.3f): search '%s' in '%s' found %d documents} % [res[:time], search, from, res[:total]]
123
+ )
124
+ res[:matches].map{|doc| {:id => doc[:doc]}}
125
+ end
126
+
127
+ ##
128
+ # Sphinx search query string from properties (fields).
129
+ #
130
+ # If the query has no conditions an '' empty string will be generated possibly triggering Sphinx's full scan
131
+ # mode.
132
+ #
133
+ # @see http://www.sphinxsearch.com/doc.html#searching
134
+ # @see http://www.sphinxsearch.com/doc.html#conf-docinfo
135
+ # @param [DataMapper::Query]
136
+ # @return [String]
137
+ def search_query(query)
138
+ match = []
139
+
140
+ if query.conditions.empty?
141
+ match << ''
142
+ else
143
+ # TODO: This needs to be altered by match mode since not everything is supported in different match modes.
144
+ query.conditions.each do |operator, property, value|
145
+ next if property.kind_of? Sphinx::Attribute # Filters are added elsewhere.
146
+ # TODO: Why does my gem riddle differ from the vendor riddle that comes with ts?
147
+ # escaped_value = Riddle.escape(value)
148
+ escaped_value = value.to_s.gsub(/[\(\)\|\-!@~"&\/]/){|char| "\\#{char}"}
149
+ match << case operator
150
+ when :eql, :like then "@#{property.field} #{escaped_value}"
151
+ when :not then "@#{property.field} -#{escaped_value}"
152
+ when :lt, :gt, :lte, :gte
153
+ DataMapper.logger.warn('Sphinx: Query properties with lt, gt, lte, gte are treated as .eql')
154
+ "@#{name} #{escaped_value}"
155
+ when :raw
156
+ "#{property}"
157
+ end
158
+ end
159
+ end
160
+ match.join(' ')
161
+ end
162
+
163
+ ##
164
+ # Sphinx search query filters from attributes.
165
+ # @param [DataMapper::Query]
166
+ # @return [Array]
167
+ def search_filters(query)
168
+ filters = []
169
+ query.conditions.each do |operator, attribute, value|
170
+ next unless attribute.kind_of? Sphinx::Attribute
171
+ # TODO: Value cast to uint, bool, str2ordinal, float
172
+ filters << case operator
173
+ when :eql, :like then Riddle::Client::Filter.new(attribute.name.to_s, filter_value(value))
174
+ when :not then Riddle::Client::Filter.new(attribute.name.to_s, filter_value(value), true)
175
+ else
176
+ error = "Sphinx: Query attributes do not support the #{operator} operator"
177
+ DataMapper.logger.error(error)
178
+ raise error # TODO: RuntimeError subclass and more information about the actual query.
179
+ end
180
+ end
181
+ filters
182
+ end
183
+
184
+ ##
185
+ # Order by attributes.
186
+ #
187
+ # @return [String or Symbol]
188
+ def search_order(query)
189
+ by = []
190
+ # TODO: How do you tell the difference between the default query order and someone explicitly asking for
191
+ # sorting by the primary key?
192
+ query.order.each do |order|
193
+ next unless order.property.kind_of? Sphinx::Attribute
194
+ by << [order.property.field, order.direction].join(' ')
195
+ end
196
+ by.empty? ? nil : by.join(', ')
197
+ end
198
+
199
+ # TODO: Move this to Attribute#dump.
200
+ # This is ninja'd straight from TS just to get things going.
201
+ def filter_value(value)
202
+ case value
203
+ when Range
204
+ value.first.is_a?(Time) ? value.first.to_i..value.last.to_i : value
205
+ when Array
206
+ value.collect { |val| val.is_a?(Time) ? val.to_i : val }
207
+ else
208
+ Array(value)
209
+ end
210
+ end
211
+ end # Adapter
212
+ end # Sphinx
213
+
214
+ # Keep magic in DataMapper#setup happy.
215
+ SphinxAdapter = Sphinx::Adapter
216
+ end # Adapters
217
+ end # DataMapper
218
+
@@ -0,0 +1,39 @@
1
+ module DataMapper
2
+ module Adapters
3
+ module Sphinx
4
+
5
+ ##
6
+ # Define a Sphinx attribute.
7
+ #
8
+ # Supports only a subset of DataMapper::Property types that can be used as Sphinx attributes.
9
+ #
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
17
+ class Attribute < Property
18
+
19
+ # DataMapper types supported as Sphinx attributes.
20
+ TYPES = [
21
+ TrueClass, # sql_attr_bool
22
+ String, # sql_attr_str2ordinal
23
+ # DataMapper::Types::Text,
24
+ Float, # sql_attr_float
25
+ Integer, # sql_attr_uint
26
+ # BigDecimal, # sql_attr_float?
27
+ DateTime, # sql_attr_timestamp
28
+ Date, # sql_attr_timestamp
29
+ Time, # sql_attr_timestamp
30
+ # Object,
31
+ # Class,
32
+ # DataMapper::Types::Discriminator,
33
+ DataMapper::Types::Serial # sql_attr_uint
34
+ ]
35
+
36
+ end # Attribute
37
+ end # Sphinx
38
+ end # Adapters
39
+ end # DataMapper
@@ -0,0 +1,86 @@
1
+ require 'rubygems'
2
+
3
+ gem 'riddle', '~> 0.9'
4
+ require 'riddle'
5
+
6
+ module DataMapper
7
+ module Adapters
8
+ module Sphinx
9
+ class Client
10
+ include Extlib::Assertions
11
+
12
+ def initialize(uri_or_options = {})
13
+ @config = Sphinx::Config.new(uri_or_options)
14
+ end
15
+
16
+ ##
17
+ # Search one or more indexes.
18
+ #
19
+ # @param [String] query The sphinx query string.
20
+ # @param [Array, String] indexes A string or array of indexes to search. Default is '*' (all).
21
+ # @param [Hash] options Any options you'd like to pass through to Riddle::Client.
22
+ # @see Riddle::Client
23
+ def search(query, indexes = '*', options = {})
24
+ indexes = indexes.join(' ') if indexes.kind_of?(Array)
25
+
26
+ client = Riddle::Client.new(@config.address, @config.port)
27
+ options.each{|k, v| client.method("#{k}=".to_sym).call(v) if client.respond_to?("#{k}=".to_sym)}
28
+ client.query(query, indexes.to_s)
29
+ end
30
+
31
+ ##
32
+ # Index one or more indexes.
33
+ #
34
+ # @param [Array, String] indexes Defaults to --all if indexes is nil or '*'.
35
+ def index(indexes = nil, options = {})
36
+ indexes = indexes.join(' ') if indexes.kind_of?(Array)
37
+
38
+ command = @config.indexer_bin
39
+ command << " --rotate" if running?
40
+ command << ((indexes.nil? || indexes == '*') ? ' --all' : " #{indexes.to_s}")
41
+ warn "Sphinx: Indexer #{$1}" if `#{command}` =~ /(?:error|fatal|warning):?\s*([^\n]+)/i
42
+ end
43
+
44
+ protected
45
+
46
+ ##
47
+ # Is the client running.
48
+ #
49
+ # Tests the address and port set in the configuration file.
50
+ def running?
51
+ !!TCPSocket.new(@config.address, @config.port) rescue nil
52
+ end
53
+ end # Client
54
+
55
+ ##
56
+ # Managed searchd if you don't already have god/monit doing the job for you.
57
+ #
58
+ # Requires you have daemon_controller installed.
59
+ # @see http://github.com/FooBarWidget/daemon_controller/tree/master
60
+ class ManagedClient < Client
61
+ def initialize(url_or_options = {})
62
+ super
63
+
64
+ # Fire up searchd.
65
+ require 'daemon_controller'
66
+ @client = DaemonController.new(
67
+ :identifier => 'Sphinx searchd',
68
+ :start_command => @config.searchd_bin,
69
+ :stop_command => "#{@config.searchd_bin} --stop",
70
+ :ping_command => method(:running?),
71
+ :pid_file => @config.pid_file,
72
+ :log_file => @config.log
73
+ )
74
+ end
75
+
76
+ def search(*args)
77
+ @client.connect{super}
78
+ end
79
+
80
+ def stop
81
+ @client.stop if @client.running?
82
+ end
83
+ end # ManagedClient
84
+ end # Sphinx
85
+ end # Adapters
86
+ end # DataMapper