zinx 0.0.1
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/lib/sphinx/sphinx.rb +6 -0
- data/lib/sphinx/sphinx/client.rb +1125 -0
- data/lib/sphinx/sphinx/request.rb +50 -0
- data/lib/sphinx/sphinx/response.rb +69 -0
- data/lib/zinx.rb +274 -0
- data/test/test.rb +10 -0
- metadata +51 -0
@@ -0,0 +1,50 @@
|
|
1
|
+
module Sphinx
|
2
|
+
# Pack ints, floats, strings, and arrays to internal representation
|
3
|
+
# needed by Sphinx search engine.
|
4
|
+
class Request
|
5
|
+
# Initialize new request.
|
6
|
+
def initialize
|
7
|
+
@request = ''
|
8
|
+
end
|
9
|
+
|
10
|
+
# Put int(s) to request.
|
11
|
+
def put_int(*ints)
|
12
|
+
ints.each { |i| @request << [i].pack('N') }
|
13
|
+
end
|
14
|
+
|
15
|
+
# Put 64-bit int(s) to request.
|
16
|
+
def put_int64(*ints)
|
17
|
+
ints.each { |i| @request << [i].pack('q').reverse }#[i >> 32, i & ((1 << 32) - 1)].pack('NN') }
|
18
|
+
end
|
19
|
+
|
20
|
+
# Put string(s) to request (first length, then the string itself).
|
21
|
+
def put_string(*strings)
|
22
|
+
strings.each { |s| @request << [s.length].pack('N') + s }
|
23
|
+
end
|
24
|
+
|
25
|
+
# Put float(s) to request.
|
26
|
+
def put_float(*floats)
|
27
|
+
floats.each do |f|
|
28
|
+
t1 = [f].pack('f') # machine order
|
29
|
+
t2 = t1.unpack('L*').first # int in machine order
|
30
|
+
@request << [t2].pack('N')
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Put array of ints to request (first length, then the array itself)
|
35
|
+
def put_int_array(arr)
|
36
|
+
put_int arr.length, *arr
|
37
|
+
end
|
38
|
+
|
39
|
+
# Put array of 64-bit ints to request (first length, then the array itself)
|
40
|
+
def put_int64_array(arr)
|
41
|
+
put_int arr.length
|
42
|
+
put_int64(*arr)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Returns the entire message
|
46
|
+
def to_s
|
47
|
+
@request
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module Sphinx
|
2
|
+
# Unpack internal Sphinx representation of ints, floats, strings, and arrays.
|
3
|
+
# needed by Sphinx search engine.
|
4
|
+
class Response
|
5
|
+
# Initialize new request.
|
6
|
+
def initialize(response)
|
7
|
+
@response = response
|
8
|
+
@position = 0
|
9
|
+
@size = response.length
|
10
|
+
end
|
11
|
+
|
12
|
+
# Gets current stream position.
|
13
|
+
def position
|
14
|
+
@position
|
15
|
+
end
|
16
|
+
|
17
|
+
# Gets response size.
|
18
|
+
def size
|
19
|
+
@size
|
20
|
+
end
|
21
|
+
|
22
|
+
# Returns <tt>true</tt> when response stream is out.
|
23
|
+
def eof?
|
24
|
+
@position >= @size
|
25
|
+
end
|
26
|
+
|
27
|
+
# Get int from stream.
|
28
|
+
def get_int
|
29
|
+
raise EOFError if @position + 4 > @size
|
30
|
+
value = @response[@position, 4].unpack('N*').first
|
31
|
+
@position += 4
|
32
|
+
return value
|
33
|
+
end
|
34
|
+
|
35
|
+
# Get 64-bit int from stream.
|
36
|
+
def get_int64
|
37
|
+
raise EOFError if @position + 8 > @size
|
38
|
+
hi, lo = @response[@position, 8].unpack('N*N*')
|
39
|
+
@position += 8
|
40
|
+
return (hi << 32) + lo
|
41
|
+
end
|
42
|
+
|
43
|
+
# Get array of <tt>count</tt> ints from stream.
|
44
|
+
def get_ints(count)
|
45
|
+
length = 4 * count
|
46
|
+
raise EOFError if @position + length > @size
|
47
|
+
values = @response[@position, length].unpack('N*' * count)
|
48
|
+
@position += length
|
49
|
+
return values
|
50
|
+
end
|
51
|
+
|
52
|
+
# Get string from stream.
|
53
|
+
def get_string
|
54
|
+
length = get_int
|
55
|
+
raise EOFError if @position + length > @size
|
56
|
+
value = length > 0 ? @response[@position, length] : ''
|
57
|
+
@position += length
|
58
|
+
return value
|
59
|
+
end
|
60
|
+
|
61
|
+
# Get float from stream.
|
62
|
+
def get_float
|
63
|
+
raise EOFError if @position + 4 > @size
|
64
|
+
uval = @response[@position, 4].unpack('N*').first;
|
65
|
+
@position += 4
|
66
|
+
return ([uval].pack('L')).unpack('f*').first
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
data/lib/zinx.rb
ADDED
@@ -0,0 +1,274 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/sphinx/sphinx'
|
2
|
+
|
3
|
+
module Zinx
|
4
|
+
|
5
|
+
class Client < Sphinx::Client
|
6
|
+
|
7
|
+
class << self
|
8
|
+
attr_reader :client, # Sphinx::Client instance
|
9
|
+
:query, # Query text specified by the user
|
10
|
+
:index_name, # Index name to run the query against
|
11
|
+
:multiple_queries, # Boolean if needs to run more then one query (add_query)
|
12
|
+
:field_weights, # Hash of field weights
|
13
|
+
:index_weights, # Hash of index weights
|
14
|
+
:results # Results from Sphinx
|
15
|
+
|
16
|
+
def filter(field, value, exclude = false)
|
17
|
+
@client.SetFilter(field, value.instance_of?(Array) ? value : [value], exclude)
|
18
|
+
end
|
19
|
+
|
20
|
+
def filter_range(field, min, max, exclude = false)
|
21
|
+
@client.SetFilterRange(field, min, max, exclude)
|
22
|
+
end
|
23
|
+
|
24
|
+
def filter_float_range(field, min, max, exclude = false)
|
25
|
+
@client.SetFilterFloatRange(field, min, max, exclude)
|
26
|
+
end
|
27
|
+
|
28
|
+
def sort(mode, value = '')
|
29
|
+
@client.SetSortMode(mode, value)
|
30
|
+
end
|
31
|
+
|
32
|
+
def group(mode, value)
|
33
|
+
@client.SetGroupBy(value, mode)
|
34
|
+
end
|
35
|
+
|
36
|
+
def group_distinct(field)
|
37
|
+
@client.SetGroupDistinct(field)
|
38
|
+
end
|
39
|
+
|
40
|
+
def select(value)
|
41
|
+
@client.SetSelect(value)
|
42
|
+
end
|
43
|
+
|
44
|
+
def last_error
|
45
|
+
@client.GetLastError
|
46
|
+
end
|
47
|
+
|
48
|
+
def last_warning
|
49
|
+
@client.GetLastWarning
|
50
|
+
end
|
51
|
+
|
52
|
+
def server(host, port)
|
53
|
+
@client.SetServer(host, port)
|
54
|
+
end
|
55
|
+
|
56
|
+
def limits(offset, limit, max = 0, cutoff = 0)
|
57
|
+
@client.SetLimits(offset, limit, max, cutoff)
|
58
|
+
end
|
59
|
+
|
60
|
+
def max_query_time(time)
|
61
|
+
@client.SetMaxQueryTime(time)
|
62
|
+
end
|
63
|
+
|
64
|
+
def match_mode(mode)
|
65
|
+
@client.SetMatchMode(mode)
|
66
|
+
end
|
67
|
+
|
68
|
+
def ranking_mode(mode)
|
69
|
+
@client.SetRankingMode(mode)
|
70
|
+
end
|
71
|
+
|
72
|
+
def field_weight(field, weight)
|
73
|
+
@field_weights[field] = weight
|
74
|
+
end
|
75
|
+
|
76
|
+
def field_weights(hash)
|
77
|
+
@field_weights = hash
|
78
|
+
end
|
79
|
+
|
80
|
+
def index_weight(index, weight)
|
81
|
+
@index_weights[index] = weight
|
82
|
+
end
|
83
|
+
|
84
|
+
def index_weights(hash)
|
85
|
+
@index_weights = hash
|
86
|
+
end
|
87
|
+
|
88
|
+
def id_range(min, max)
|
89
|
+
@client.SetIDRange(min, max)
|
90
|
+
end
|
91
|
+
|
92
|
+
def geo_anchor(attr_lat, attr_lng, lat, lng)
|
93
|
+
@client.Set(attr_lat, attr_lng, lat, lng)
|
94
|
+
end
|
95
|
+
|
96
|
+
def retries(count, delay = 0)
|
97
|
+
@client.SetRetries(count, delay)
|
98
|
+
end
|
99
|
+
|
100
|
+
def override(field, type, values)
|
101
|
+
@client.SetOverride(field, type, values)
|
102
|
+
end
|
103
|
+
|
104
|
+
def reset
|
105
|
+
@client.ResetFilters
|
106
|
+
@client.ResetGroupBy
|
107
|
+
@client.ResetOverrides
|
108
|
+
end
|
109
|
+
|
110
|
+
def reset_filters
|
111
|
+
@client.ResetFilters
|
112
|
+
end
|
113
|
+
|
114
|
+
def reset_groups
|
115
|
+
@client.ResetGroupBy
|
116
|
+
end
|
117
|
+
|
118
|
+
def reset_overrides
|
119
|
+
@client.ResetOverrides
|
120
|
+
end
|
121
|
+
|
122
|
+
def build_excerpts(docs, index, words, opts = {})
|
123
|
+
init
|
124
|
+
@client.BuildExcerpts(docs, index, words, opts)
|
125
|
+
end
|
126
|
+
|
127
|
+
def excerpts(docs, words, opts = {})
|
128
|
+
run if @results.empty?
|
129
|
+
@client.BuildExcerpts(docs, @index_name, words, opts)
|
130
|
+
end
|
131
|
+
|
132
|
+
def build_keywords(query, index, hits)
|
133
|
+
init
|
134
|
+
@client.BuildKeywords(query, index, hits)
|
135
|
+
end
|
136
|
+
|
137
|
+
def update(index, attrs, values, mva = false)
|
138
|
+
init
|
139
|
+
@client.UpdateAttributes(index, attrs, values, mva)
|
140
|
+
end
|
141
|
+
|
142
|
+
def results
|
143
|
+
run if @results.empty?
|
144
|
+
@results
|
145
|
+
end
|
146
|
+
|
147
|
+
# syntax sugar for results[0].matches when using only one query
|
148
|
+
# you don't even have to call 'run' before using this
|
149
|
+
def matches
|
150
|
+
run if @results.empty?
|
151
|
+
!@multiple_queries && @results.count > 0 ? @results[0].matches : []
|
152
|
+
end
|
153
|
+
|
154
|
+
# must call run before accessing search results
|
155
|
+
def run
|
156
|
+
# set the weights
|
157
|
+
@client.SetFieldWeights(@field_weights) unless @field_weights.empty?
|
158
|
+
@client.SetIndexWeights(@index_weights) unless @index_weights.empty?
|
159
|
+
|
160
|
+
# run the query
|
161
|
+
if @multiple_queries
|
162
|
+
q = @client.RunQueries
|
163
|
+
q.each do |result|
|
164
|
+
@results << Result.new(result)
|
165
|
+
end
|
166
|
+
else
|
167
|
+
q = @client.Query(@query, @index_name)
|
168
|
+
@results << Result.new(q)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
# add query for multiple queries
|
173
|
+
def add_query
|
174
|
+
@multiple_queries = true
|
175
|
+
@client.AddQuery(@query, @index_name)
|
176
|
+
reset
|
177
|
+
end
|
178
|
+
|
179
|
+
# Entry point for searches
|
180
|
+
# Valid params are:
|
181
|
+
# :server => Sphinx server address (defaults to 'localhost')
|
182
|
+
# :port => Sphinx port number (defaults to 9312)
|
183
|
+
# :match_mode => Sphinx matching mode (defaults to Zinx::SPH_MATCH_EXTENDED)
|
184
|
+
# :index_name => Name of the index to search on
|
185
|
+
def search(query, params = {}, &block)
|
186
|
+
params[:query] = query
|
187
|
+
init(params)
|
188
|
+
if !block_given?
|
189
|
+
run
|
190
|
+
return @results
|
191
|
+
else
|
192
|
+
yield
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
def init(params = {})
|
197
|
+
@client = Client.new
|
198
|
+
@client.SetServer(params[:server] || 'localhost', params[:port] || 9312)
|
199
|
+
@client.SetMatchMode(params[:match_mode] || Zinx::Client::SPH_MATCH_EXTENDED)
|
200
|
+
@query = params[:query]
|
201
|
+
@index_name = params[:index_name] || "*"
|
202
|
+
@multiple_queries = false
|
203
|
+
@results = []
|
204
|
+
@field_weights = {}
|
205
|
+
@index_weights = {}
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
class Match
|
211
|
+
def initialize(hash)
|
212
|
+
@match = hash
|
213
|
+
end
|
214
|
+
|
215
|
+
def each(&block)
|
216
|
+
@match.each do |m|
|
217
|
+
block.call m
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
def method_missing(method)
|
222
|
+
if ['groupby', 'count', 'expr'].include?("#{method}")
|
223
|
+
@match["attrs"]["@#{method}"]
|
224
|
+
elsif ['id', 'weight', 'attrs'].include?("#{method}")
|
225
|
+
@match["#{method}"]
|
226
|
+
else
|
227
|
+
@match["attrs"]["#{method}"]
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
class Result
|
233
|
+
attr_reader :matches
|
234
|
+
|
235
|
+
def initialize(sphinx_hash)
|
236
|
+
@matches = []
|
237
|
+
@sphinx_hash = sphinx_hash
|
238
|
+
@sphinx_hash["matches"].each do |match|
|
239
|
+
@matches << Match.new(match)
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
def method_missing(method)
|
244
|
+
@sphinx_hash["#{method}"]
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
module Search
|
249
|
+
class << self
|
250
|
+
attr_accessor :target
|
251
|
+
|
252
|
+
def delegate(*methods)
|
253
|
+
methods.each do |method|
|
254
|
+
define_method(method) do |*args, &block|
|
255
|
+
Search.target.send(method, *args, &block)
|
256
|
+
end
|
257
|
+
private method
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
self.target = Zinx::Client
|
263
|
+
|
264
|
+
delegate :filter, :filter_range, :filter_float_range, :sort, :group, :group_distinct,
|
265
|
+
:select, :last_error, :last_warning, :server, :limits, :max_query_time, :match_mode,
|
266
|
+
:ranking_mode, :field_weight, :field_weights, :index_weight, :index_weights,
|
267
|
+
:id_range, :geo_anchor, :retries, :override, :reset, :reset_filter, :reset_groups,
|
268
|
+
:reset_overrides, :build_excerpts, :excerpts, :build_keywords, :update, :results,
|
269
|
+
:matches, :run, :add_query, :search, :init
|
270
|
+
end
|
271
|
+
|
272
|
+
end
|
273
|
+
|
274
|
+
extend Zinx::Search
|
data/test/test.rb
ADDED
metadata
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: zinx
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Gabriel Hora
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-03-15 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: Simple DSL for Sphinx Search Server
|
15
|
+
email: gabrielhora@gmail.com
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- lib/sphinx/sphinx/client.rb
|
21
|
+
- lib/sphinx/sphinx/request.rb
|
22
|
+
- lib/sphinx/sphinx/response.rb
|
23
|
+
- lib/sphinx/sphinx.rb
|
24
|
+
- lib/zinx.rb
|
25
|
+
- test/test.rb
|
26
|
+
homepage: http://www.polyglotdba.com/zinx
|
27
|
+
licenses: []
|
28
|
+
post_install_message:
|
29
|
+
rdoc_options: []
|
30
|
+
require_paths:
|
31
|
+
- lib
|
32
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
requirements: []
|
45
|
+
rubyforge_project:
|
46
|
+
rubygems_version: 1.8.17
|
47
|
+
signing_key:
|
48
|
+
specification_version: 3
|
49
|
+
summary: Simple DSL for Sphinx Search Server
|
50
|
+
test_files:
|
51
|
+
- test/test.rb
|