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 +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
|