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
@@ -2,21 +2,25 @@ module DataMapper
|
|
2
2
|
module Adapters
|
3
3
|
module Sphinx
|
4
4
|
|
5
|
-
##
|
6
5
|
# Sphinx extended search query string from DataMapper query.
|
7
6
|
class Query
|
8
7
|
include Extlib::Assertions
|
9
8
|
|
10
|
-
|
11
|
-
# Sphinx extended search query string from DataMapper query.
|
9
|
+
# Initialize a new extended Sphinx query from a DataMapper::Query object.
|
12
10
|
#
|
13
11
|
# If the query has no conditions an '' empty string will be generated possibly triggering Sphinx's full scan
|
14
12
|
# mode.
|
15
13
|
#
|
16
|
-
#
|
17
|
-
#
|
18
|
-
#
|
19
|
-
#
|
14
|
+
# ==== See
|
15
|
+
# * http://www.sphinxsearch.com/doc.html#searching
|
16
|
+
# * http://www.sphinxsearch.com/doc.html#conf-docinfo
|
17
|
+
# * http://www.sphinxsearch.com/doc.html#extended-syntax
|
18
|
+
#
|
19
|
+
# ==== Raises
|
20
|
+
# NotImplementedError:: DataMapper operators that can't be expressed in the extended sphinx query syntax.
|
21
|
+
#
|
22
|
+
# ==== Parameters
|
23
|
+
# query<DataMapper::Query>:: DataMapper query object.
|
20
24
|
def initialize(query)
|
21
25
|
assert_kind_of 'query', query, DataMapper::Query
|
22
26
|
@query = []
|
@@ -38,18 +42,20 @@ module DataMapper
|
|
38
42
|
end
|
39
43
|
end
|
40
44
|
|
41
|
-
|
42
|
-
# The extended sphinx query string.
|
43
|
-
# @return [String]
|
45
|
+
# ==== Returns
|
46
|
+
# String:: The extended sphinx query string.
|
44
47
|
def to_s
|
45
48
|
@query.join(' ')
|
46
49
|
end
|
47
50
|
|
48
51
|
protected
|
49
|
-
##
|
50
52
|
# Normalize and escape DataMapper query value(s) to escaped sphinx query values.
|
51
|
-
#
|
52
|
-
#
|
53
|
+
#
|
54
|
+
# ==== Parameters
|
55
|
+
# value<String, Array>:: The query value.
|
56
|
+
#
|
57
|
+
# ==== Returns
|
58
|
+
# Array:: An array of one or more query values.
|
53
59
|
def normalize_value(value)
|
54
60
|
[value].flatten.map do |v|
|
55
61
|
v.to_s.gsub(/[\(\)\|\-!@~"&\/]/){|char| "\\#{char}"}
|
@@ -2,8 +2,7 @@ module DataMapper
|
|
2
2
|
module Adapters
|
3
3
|
module Sphinx
|
4
4
|
|
5
|
-
|
6
|
-
# Declare Sphinx indexes in your resource.
|
5
|
+
# Declare Sphinx indexes and attributes in your resource.
|
7
6
|
#
|
8
7
|
# model Items
|
9
8
|
# include DataMapper::SphinxResource
|
@@ -35,15 +34,17 @@ module DataMapper
|
|
35
34
|
model.instance_variable_set(:@sphinx_attributes, {})
|
36
35
|
end
|
37
36
|
|
38
|
-
##
|
39
37
|
# Defines a sphinx index on the resource.
|
40
38
|
#
|
41
39
|
# Indexes are naturally ordered, with delta indexes at the end of the list so that duplicate document IDs in
|
42
40
|
# delta indexes override your main indexes.
|
43
41
|
#
|
44
|
-
#
|
45
|
-
#
|
46
|
-
#
|
42
|
+
# ==== See
|
43
|
+
# * DataMapper::Adapters::Sphinx::Index
|
44
|
+
#
|
45
|
+
# ==== Parameters
|
46
|
+
# name<Symbol>:: The name of a sphinx index to search for this resource.
|
47
|
+
# options<Hash>:: A hash of available index options.
|
47
48
|
def index(name, options = {})
|
48
49
|
index = Index.new(self, name, options)
|
49
50
|
indexes = sphinx_indexes(repository_name)
|
@@ -58,19 +59,23 @@ module DataMapper
|
|
58
59
|
index
|
59
60
|
end
|
60
61
|
|
61
|
-
##
|
62
62
|
# List of declared sphinx indexes for this model.
|
63
|
+
#
|
64
|
+
# ==== Returns
|
65
|
+
# Array<DataMapper::Adapters::Sphinx::Index>
|
63
66
|
def sphinx_indexes(repository_name = default_repository_name)
|
64
67
|
@sphinx_indexes[repository_name] ||= []
|
65
68
|
end
|
66
69
|
|
67
|
-
##
|
68
70
|
# Defines a sphinx attribute on the resource.
|
69
71
|
#
|
70
|
-
#
|
71
|
-
#
|
72
|
-
#
|
73
|
-
#
|
72
|
+
# ==== See
|
73
|
+
# DataMapper::Adapters::Sphinx::Attribute
|
74
|
+
#
|
75
|
+
# ==== Parameters
|
76
|
+
# name<Symbol>:: The name of a sphinx attribute to order/restrict by for this resource.
|
77
|
+
# type<Class>:: The type to define this attribute as.
|
78
|
+
# options<Hash>:: An optional hash of attribute options.
|
74
79
|
def attribute(name, type, options = {})
|
75
80
|
# Attributes are just properties without a getter/setter in the model.
|
76
81
|
# This keeps DataMapper::Query happy when building queries.
|
@@ -79,8 +84,10 @@ module DataMapper
|
|
79
84
|
attribute
|
80
85
|
end
|
81
86
|
|
82
|
-
##
|
83
87
|
# List of declared sphinx attributes for this model.
|
88
|
+
#
|
89
|
+
# ==== Returns
|
90
|
+
# Array<DataMapper::Adapters::Sphinx::Attribute>
|
84
91
|
def sphinx_attributes(repository_name = default_repository_name)
|
85
92
|
properties(repository_name).grep{|p| p.kind_of? Sphinx::Attribute}
|
86
93
|
end
|
data/lib/dm-sphinx-adapter.rb
CHANGED
@@ -1,19 +1,22 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
|
3
3
|
# TODO: Hide the shitload of dm-core warnings or at least try to?
|
4
|
-
$VERBOSE = nil
|
5
|
-
gem 'dm-core', '~> 0.9.7'
|
6
|
-
require 'dm-core'
|
4
|
+
old_verbose, $VERBOSE = $VERBOSE, nil
|
5
|
+
gem 'dm-core', '~> 0.9.7'
|
6
|
+
require 'dm-core'
|
7
|
+
$VERBOSE = old_verbose
|
7
8
|
|
8
|
-
# TODO: I think I might move everything to DataMapper::Sphinx::* and ignore the default naming convention.
|
9
9
|
require 'pathname'
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
10
|
+
lib = Pathname(__FILE__).dirname.expand_path
|
11
|
+
dir = lib / 'dm-sphinx-adapter'
|
12
|
+
|
13
|
+
# Bundled Riddle since the gem is very old and we don't need any of the config generation stuff.
|
14
|
+
$:.unshift lib
|
15
|
+
require 'riddle'
|
16
|
+
|
17
|
+
# TODO: Require farms suck. Do something about it.
|
15
18
|
require dir / 'adapter'
|
16
|
-
require dir / 'index'
|
17
19
|
require dir / 'attribute'
|
20
|
+
require dir / 'index'
|
21
|
+
require dir / 'query'
|
18
22
|
require dir / 'resource'
|
19
|
-
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Riddle
|
2
|
+
class Client
|
3
|
+
# Used for querying Sphinx.
|
4
|
+
class Filter
|
5
|
+
attr_accessor :attribute, :values, :exclude
|
6
|
+
|
7
|
+
# Attribute name, values (which can be an array or a range), and whether
|
8
|
+
# the filter should be exclusive.
|
9
|
+
def initialize(attribute, values, exclude=false)
|
10
|
+
@attribute, @values, @exclude = attribute, values, exclude
|
11
|
+
end
|
12
|
+
|
13
|
+
def exclude?
|
14
|
+
self.exclude
|
15
|
+
end
|
16
|
+
|
17
|
+
# Returns the message for this filter to send to the Sphinx service
|
18
|
+
def query_message
|
19
|
+
message = Message.new
|
20
|
+
|
21
|
+
message.append_string self.attribute.to_s
|
22
|
+
case self.values
|
23
|
+
when Range
|
24
|
+
if self.values.first.is_a?(Float) && self.values.last.is_a?(Float)
|
25
|
+
message.append_int FilterTypes[:float_range]
|
26
|
+
message.append_floats self.values.first, self.values.last
|
27
|
+
else
|
28
|
+
message.append_int FilterTypes[:range]
|
29
|
+
message.append_ints self.values.first, self.values.last
|
30
|
+
end
|
31
|
+
when Array
|
32
|
+
message.append_int FilterTypes[:values]
|
33
|
+
message.append_int self.values.length
|
34
|
+
# using to_f is a hack from the php client - to workaround 32bit
|
35
|
+
# signed ints on x32 platforms
|
36
|
+
message.append_ints *self.values.collect { |val|
|
37
|
+
case val
|
38
|
+
when TrueClass
|
39
|
+
1.0
|
40
|
+
when FalseClass
|
41
|
+
0.0
|
42
|
+
else
|
43
|
+
val.to_f
|
44
|
+
end
|
45
|
+
}
|
46
|
+
end
|
47
|
+
message.append_int self.exclude? ? 1 : 0
|
48
|
+
|
49
|
+
message.to_s
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Riddle
|
2
|
+
class Client
|
3
|
+
# This class takes care of the translation of ints, strings and arrays to
|
4
|
+
# the format required by the Sphinx service.
|
5
|
+
class Message
|
6
|
+
def initialize
|
7
|
+
@message = ""
|
8
|
+
@size_method = @message.respond_to?(:bytesize) ? :bytesize : :length
|
9
|
+
end
|
10
|
+
|
11
|
+
# Append raw data (only use if you know what you're doing)
|
12
|
+
def append(*args)
|
13
|
+
return if args.length == 0
|
14
|
+
|
15
|
+
args.each { |arg| @message << arg }
|
16
|
+
end
|
17
|
+
|
18
|
+
# Append a string's length, then the string itself
|
19
|
+
def append_string(str)
|
20
|
+
@message << [str.send(@size_method)].pack('N') + str
|
21
|
+
end
|
22
|
+
|
23
|
+
# Append an integer
|
24
|
+
def append_int(int)
|
25
|
+
@message << [int].pack('N')
|
26
|
+
end
|
27
|
+
|
28
|
+
def append_64bit_int(int)
|
29
|
+
@message << [int >> 32, int & 0xFFFFFFFF].pack('NN')
|
30
|
+
end
|
31
|
+
|
32
|
+
# Append a float
|
33
|
+
def append_float(float)
|
34
|
+
@message << [float].pack('f').unpack('L*').pack("N")
|
35
|
+
end
|
36
|
+
|
37
|
+
# Append multiple integers
|
38
|
+
def append_ints(*ints)
|
39
|
+
ints.each { |int| append_int(int) }
|
40
|
+
end
|
41
|
+
|
42
|
+
def append_64bit_ints(*ints)
|
43
|
+
ints.each { |int| append_64bit_int(int) }
|
44
|
+
end
|
45
|
+
|
46
|
+
# Append multiple floats
|
47
|
+
def append_floats(*floats)
|
48
|
+
floats.each { |float| append_float(float) }
|
49
|
+
end
|
50
|
+
|
51
|
+
# Append an array of strings - first appends the length of the array,
|
52
|
+
# then each item's length and value.
|
53
|
+
def append_array(array)
|
54
|
+
append_int(array.length)
|
55
|
+
|
56
|
+
array.each { |item| append_string(item) }
|
57
|
+
end
|
58
|
+
|
59
|
+
# Returns the entire message
|
60
|
+
def to_s
|
61
|
+
@message
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module Riddle
|
2
|
+
class Client
|
3
|
+
# Used to interrogate responses from the Sphinx daemon. Keep in mind none
|
4
|
+
# of the methods here check whether the data they're grabbing are what the
|
5
|
+
# user expects - it just assumes the user knows what the data stream is
|
6
|
+
# made up of.
|
7
|
+
class Response
|
8
|
+
# Create with the data to interpret
|
9
|
+
def initialize(str)
|
10
|
+
@str = str
|
11
|
+
@marker = 0
|
12
|
+
end
|
13
|
+
|
14
|
+
# Return the next string value in the stream
|
15
|
+
def next
|
16
|
+
len = next_int
|
17
|
+
result = @str[@marker, len]
|
18
|
+
@marker += len
|
19
|
+
|
20
|
+
return result
|
21
|
+
end
|
22
|
+
|
23
|
+
# Return the next integer value from the stream
|
24
|
+
def next_int
|
25
|
+
int = @str[@marker, 4].unpack('N*').first
|
26
|
+
@marker += 4
|
27
|
+
|
28
|
+
return int
|
29
|
+
end
|
30
|
+
|
31
|
+
def next_64bit_int
|
32
|
+
high, low = @str[@marker, 8].unpack('N*N*')[0..1]
|
33
|
+
@marker += 8
|
34
|
+
|
35
|
+
return (high << 32) + low
|
36
|
+
end
|
37
|
+
|
38
|
+
# Return the next float value from the stream
|
39
|
+
def next_float
|
40
|
+
float = @str[@marker, 4].unpack('N*').pack('L').unpack('f*').first
|
41
|
+
@marker += 4
|
42
|
+
|
43
|
+
return float
|
44
|
+
end
|
45
|
+
|
46
|
+
# Returns an array of string items
|
47
|
+
def next_array
|
48
|
+
count = next_int
|
49
|
+
items = []
|
50
|
+
for i in 0...count
|
51
|
+
items << self.next
|
52
|
+
end
|
53
|
+
|
54
|
+
return items
|
55
|
+
end
|
56
|
+
|
57
|
+
# Returns an array of int items
|
58
|
+
def next_int_array
|
59
|
+
count = next_int
|
60
|
+
items = []
|
61
|
+
for i in 0...count
|
62
|
+
items << self.next_int
|
63
|
+
end
|
64
|
+
|
65
|
+
return items
|
66
|
+
end
|
67
|
+
|
68
|
+
def next_float_array
|
69
|
+
count = next_int
|
70
|
+
items = []
|
71
|
+
for i in 0...count
|
72
|
+
items << self.next_float
|
73
|
+
end
|
74
|
+
|
75
|
+
return items
|
76
|
+
end
|
77
|
+
|
78
|
+
# Returns the length of the streamed data
|
79
|
+
def length
|
80
|
+
@str.length
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|