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