shanna-dm-sphinx-adapter 0.4

Sign up to get free protection for your applications and to get access to all the features.
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