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 +17 -0
- data/LICENCE.txt +20 -0
- data/Manifest.txt +31 -0
- data/README.txt +195 -0
- data/Rakefile +23 -0
- data/dm-sphinx-adapter.gemspec +41 -0
- data/lib/dm-sphinx-adapter/adapter.rb +218 -0
- data/lib/dm-sphinx-adapter/attribute.rb +39 -0
- data/lib/dm-sphinx-adapter/client.rb +86 -0
- data/lib/dm-sphinx-adapter/config.rb +74 -0
- data/lib/dm-sphinx-adapter/config_parser.rb +67 -0
- data/lib/dm-sphinx-adapter/index.rb +25 -0
- data/lib/dm-sphinx-adapter/resource.rb +96 -0
- data/lib/dm-sphinx-adapter.rb +18 -0
- data/test/files/dm_sphinx_adapter_test.sql +21 -0
- data/test/files/resource_explicit.rb +25 -0
- data/test/files/resource_resource.rb +19 -0
- data/test/files/resource_searchable.rb +16 -0
- data/test/files/resource_storage_name.rb +11 -0
- data/test/files/resource_vanilla.rb +7 -0
- data/test/files/sphinx.conf +78 -0
- data/test/test_adapter.rb +38 -0
- data/test/test_adapter_explicit.rb +48 -0
- data/test/test_adapter_resource.rb +25 -0
- data/test/test_adapter_searchable.rb +23 -0
- data/test/test_adapter_vanilla.rb +46 -0
- data/test/test_client.rb +31 -0
- data/test/test_config.rb +75 -0
- data/test/test_config_parser.rb +29 -0
- data/test/test_type_attribute.rb +8 -0
- data/test/test_type_index.rb +8 -0
- metadata +123 -0
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
|