riddle 1.0.3 → 1.0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/README.textile +1 -0
- data/lib/riddle.rb +30 -4
- data/lib/riddle/0.9.8.rb +1 -0
- data/lib/riddle/0.9.9.rb +2 -0
- data/lib/riddle/auto_version.rb +11 -0
- data/lib/riddle/client.rb +2 -0
- data/lib/riddle/configuration.rb +3 -1
- data/lib/riddle/configuration/sql_source.rb +13 -0
- data/lib/riddle/controller.rb +29 -2
- data/spec/functional/excerpt_spec.rb +2 -2
- data/spec/functional/status_spec.rb +1 -1
- data/spec/riddle/auto_version_spec.rb +24 -0
- data/spec/riddle/client_spec.rb +11 -0
- data/spec/riddle/configuration_spec.rb +11 -0
- data/spec/riddle/controller_spec.rb +29 -0
- data/spec/riddle_spec.rb +27 -0
- data/spec/spec_helper.rb +1 -4
- data/spec/unit/client_spec.rb +1 -1
- data/spec/unit/configuration/searchd_spec.rb +2 -2
- data/spec/unit/configuration/sql_source_spec.rb +21 -0
- metadata +8 -48
- data/spec/fixtures/data/anchor.bin +0 -0
- data/spec/fixtures/data/any.bin +0 -0
- data/spec/fixtures/data/boolean.bin +0 -0
- data/spec/fixtures/data/comment.bin +0 -0
- data/spec/fixtures/data/distinct.bin +0 -0
- data/spec/fixtures/data/field_weights.bin +0 -0
- data/spec/fixtures/data/filter.bin +0 -0
- data/spec/fixtures/data/filter_array.bin +0 -0
- data/spec/fixtures/data/filter_array_exclude.bin +0 -0
- data/spec/fixtures/data/filter_boolean.bin +0 -0
- data/spec/fixtures/data/filter_floats.bin +0 -0
- data/spec/fixtures/data/filter_floats_exclude.bin +0 -0
- data/spec/fixtures/data/filter_range.bin +0 -0
- data/spec/fixtures/data/filter_range_exclude.bin +0 -0
- data/spec/fixtures/data/group.bin +0 -0
- data/spec/fixtures/data/index.bin +0 -0
- data/spec/fixtures/data/index_weights.bin +0 -0
- data/spec/fixtures/data/keywords_with_hits.bin +0 -0
- data/spec/fixtures/data/keywords_without_hits.bin +0 -0
- data/spec/fixtures/data/overrides.bin +0 -0
- data/spec/fixtures/data/phrase.bin +0 -0
- data/spec/fixtures/data/rank_mode.bin +0 -0
- data/spec/fixtures/data/select.bin +0 -0
- data/spec/fixtures/data/simple.bin +0 -0
- data/spec/fixtures/data/sort.bin +0 -0
- data/spec/fixtures/data/update_simple.bin +0 -0
- data/spec/fixtures/data/weights.bin +0 -0
- data/spec/fixtures/data_generator.0.9.8.php +0 -208
- data/spec/fixtures/data_generator.0.9.9.php +0 -225
- data/spec/fixtures/sphinx/configuration.erb +0 -38
- data/spec/fixtures/sphinx/people.spa +0 -0
- data/spec/fixtures/sphinx/people.spd +0 -0
- data/spec/fixtures/sphinx/people.sph +0 -0
- data/spec/fixtures/sphinx/people.spi +0 -0
- data/spec/fixtures/sphinx/people.spk +0 -0
- data/spec/fixtures/sphinx/people.spm +0 -0
- data/spec/fixtures/sphinx/people.spp +0 -0
- data/spec/fixtures/sphinx/searchd.log +0 -4430
- data/spec/fixtures/sphinx/searchd.query.log +0 -1234
- data/spec/fixtures/sphinx/spec.conf +0 -38
- data/spec/fixtures/sphinxapi.0.9.8.php +0 -1228
- data/spec/fixtures/sphinxapi.0.9.9.php +0 -1646
- data/spec/fixtures/sql/conf.example.yml +0 -3
- data/spec/fixtures/sql/conf.yml +0 -3
- data/spec/fixtures/sql/data.sql +0 -25000
- data/spec/fixtures/sql/structure.sql +0 -16
@@ -1,38 +0,0 @@
|
|
1
|
-
indexer
|
2
|
-
{
|
3
|
-
mem_limit = 64M
|
4
|
-
}
|
5
|
-
|
6
|
-
searchd
|
7
|
-
{
|
8
|
-
port = 3313
|
9
|
-
log = /Users/pat/Code/ruby/riddle/spec/fixtures/sphinx/searchd.log
|
10
|
-
query_log = /Users/pat/Code/ruby/riddle/spec/fixtures/sphinx/searchd.query.log
|
11
|
-
read_timeout = 5
|
12
|
-
max_children = 30
|
13
|
-
pid_file = /Users/pat/Code/ruby/riddle/spec/fixtures/sphinx/searchd.pid
|
14
|
-
}
|
15
|
-
|
16
|
-
source peoples
|
17
|
-
{
|
18
|
-
type = mysql
|
19
|
-
sql_host = localhost
|
20
|
-
sql_user = riddle
|
21
|
-
sql_pass = riddle
|
22
|
-
sql_db = riddle
|
23
|
-
|
24
|
-
sql_query = SELECT id, first_name, middle_initial, last_name, gender, street_address, city, state, postcode, email, UNIX_TIMESTAMP(birthday) AS birthday FROM people WHERE id >= $start AND id <= $end
|
25
|
-
sql_query_range = SELECT MIN(id), MAX(id) FROM people
|
26
|
-
sql_query_info = SELECT * FROM people WHERE id = $id
|
27
|
-
sql_attr_timestamp = birthday
|
28
|
-
}
|
29
|
-
|
30
|
-
index people
|
31
|
-
{
|
32
|
-
source = peoples
|
33
|
-
morphology = stem_en
|
34
|
-
path = /Users/pat/Code/ruby/riddle/spec/fixtures/sphinx/people
|
35
|
-
charset_type = utf-8
|
36
|
-
enable_star = 1
|
37
|
-
min_prefix_len = 1
|
38
|
-
}
|
@@ -1,1228 +0,0 @@
|
|
1
|
-
<?php
|
2
|
-
|
3
|
-
//
|
4
|
-
// $Id: sphinxapi.php 1418 2008-08-28 15:30:05Z shodan $
|
5
|
-
//
|
6
|
-
|
7
|
-
//
|
8
|
-
// Copyright (c) 2001-2008, Andrew Aksyonoff. All rights reserved.
|
9
|
-
//
|
10
|
-
// This program is free software; you can redistribute it and/or modify
|
11
|
-
// it under the terms of the GNU General Public License. You should have
|
12
|
-
// received a copy of the GPL license along with this program; if you
|
13
|
-
// did not, you can find it at http://www.gnu.org/
|
14
|
-
//
|
15
|
-
|
16
|
-
/////////////////////////////////////////////////////////////////////////////
|
17
|
-
// PHP version of Sphinx searchd client (PHP API)
|
18
|
-
/////////////////////////////////////////////////////////////////////////////
|
19
|
-
|
20
|
-
/// known searchd commands
|
21
|
-
define ( "SEARCHD_COMMAND_SEARCH", 0 );
|
22
|
-
define ( "SEARCHD_COMMAND_EXCERPT", 1 );
|
23
|
-
define ( "SEARCHD_COMMAND_UPDATE", 2 );
|
24
|
-
define ( "SEARCHD_COMMAND_KEYWORDS",3 );
|
25
|
-
|
26
|
-
/// current client-side command implementation versions
|
27
|
-
define ( "VER_COMMAND_SEARCH", 0x113 );
|
28
|
-
define ( "VER_COMMAND_EXCERPT", 0x100 );
|
29
|
-
define ( "VER_COMMAND_UPDATE", 0x101 );
|
30
|
-
define ( "VER_COMMAND_KEYWORDS", 0x100 );
|
31
|
-
|
32
|
-
/// known searchd status codes
|
33
|
-
define ( "SEARCHD_OK", 0 );
|
34
|
-
define ( "SEARCHD_ERROR", 1 );
|
35
|
-
define ( "SEARCHD_RETRY", 2 );
|
36
|
-
define ( "SEARCHD_WARNING", 3 );
|
37
|
-
|
38
|
-
/// known match modes
|
39
|
-
define ( "SPH_MATCH_ALL", 0 );
|
40
|
-
define ( "SPH_MATCH_ANY", 1 );
|
41
|
-
define ( "SPH_MATCH_PHRASE", 2 );
|
42
|
-
define ( "SPH_MATCH_BOOLEAN", 3 );
|
43
|
-
define ( "SPH_MATCH_EXTENDED", 4 );
|
44
|
-
define ( "SPH_MATCH_FULLSCAN", 5 );
|
45
|
-
define ( "SPH_MATCH_EXTENDED2", 6 ); // extended engine V2 (TEMPORARY, WILL BE REMOVED)
|
46
|
-
|
47
|
-
/// known ranking modes (ext2 only)
|
48
|
-
define ( "SPH_RANK_PROXIMITY_BM25", 0 ); ///< default mode, phrase proximity major factor and BM25 minor one
|
49
|
-
define ( "SPH_RANK_BM25", 1 ); ///< statistical mode, BM25 ranking only (faster but worse quality)
|
50
|
-
define ( "SPH_RANK_NONE", 2 ); ///< no ranking, all matches get a weight of 1
|
51
|
-
define ( "SPH_RANK_WORDCOUNT", 3 ); ///< simple word-count weighting, rank is a weighted sum of per-field keyword occurence counts
|
52
|
-
|
53
|
-
/// known sort modes
|
54
|
-
define ( "SPH_SORT_RELEVANCE", 0 );
|
55
|
-
define ( "SPH_SORT_ATTR_DESC", 1 );
|
56
|
-
define ( "SPH_SORT_ATTR_ASC", 2 );
|
57
|
-
define ( "SPH_SORT_TIME_SEGMENTS", 3 );
|
58
|
-
define ( "SPH_SORT_EXTENDED", 4 );
|
59
|
-
define ( "SPH_SORT_EXPR", 5 );
|
60
|
-
|
61
|
-
/// known filter types
|
62
|
-
define ( "SPH_FILTER_VALUES", 0 );
|
63
|
-
define ( "SPH_FILTER_RANGE", 1 );
|
64
|
-
define ( "SPH_FILTER_FLOATRANGE", 2 );
|
65
|
-
|
66
|
-
/// known attribute types
|
67
|
-
define ( "SPH_ATTR_INTEGER", 1 );
|
68
|
-
define ( "SPH_ATTR_TIMESTAMP", 2 );
|
69
|
-
define ( "SPH_ATTR_ORDINAL", 3 );
|
70
|
-
define ( "SPH_ATTR_BOOL", 4 );
|
71
|
-
define ( "SPH_ATTR_FLOAT", 5 );
|
72
|
-
define ( "SPH_ATTR_MULTI", 0x40000000 );
|
73
|
-
|
74
|
-
/// known grouping functions
|
75
|
-
define ( "SPH_GROUPBY_DAY", 0 );
|
76
|
-
define ( "SPH_GROUPBY_WEEK", 1 );
|
77
|
-
define ( "SPH_GROUPBY_MONTH", 2 );
|
78
|
-
define ( "SPH_GROUPBY_YEAR", 3 );
|
79
|
-
define ( "SPH_GROUPBY_ATTR", 4 );
|
80
|
-
define ( "SPH_GROUPBY_ATTRPAIR", 5 );
|
81
|
-
|
82
|
-
|
83
|
-
/// portably pack numeric to 64 unsigned bits, network order
|
84
|
-
function sphPack64 ( $v )
|
85
|
-
{
|
86
|
-
assert ( is_numeric($v) );
|
87
|
-
|
88
|
-
// x64 route
|
89
|
-
if ( PHP_INT_SIZE>=8 )
|
90
|
-
{
|
91
|
-
$i = (int)$v;
|
92
|
-
return pack ( "NN", $i>>32, $i&((1<<32)-1) );
|
93
|
-
}
|
94
|
-
|
95
|
-
// x32 route, bcmath
|
96
|
-
$x = "4294967296";
|
97
|
-
if ( function_exists("bcmul") )
|
98
|
-
{
|
99
|
-
$h = bcdiv ( $v, $x, 0 );
|
100
|
-
$l = bcmod ( $v, $x );
|
101
|
-
return pack ( "NN", (float)$h, (float)$l ); // conversion to float is intentional; int would lose 31st bit
|
102
|
-
}
|
103
|
-
|
104
|
-
// x32 route, 15 or less decimal digits
|
105
|
-
// we can use float, because its actually double and has 52 precision bits
|
106
|
-
if ( strlen($v)<=15 )
|
107
|
-
{
|
108
|
-
$f = (float)$v;
|
109
|
-
$h = (int)($f/$x);
|
110
|
-
$l = (int)($f-$x*$h);
|
111
|
-
return pack ( "NN", $h, $l );
|
112
|
-
}
|
113
|
-
|
114
|
-
// x32 route, 16 or more decimal digits
|
115
|
-
// well, let me know if you *really* need this
|
116
|
-
die ( "INTERNAL ERROR: packing more than 15-digit numeric on 32-bit PHP is not implemented yet (contact support)" );
|
117
|
-
}
|
118
|
-
|
119
|
-
|
120
|
-
/// portably unpack 64 unsigned bits, network order to numeric
|
121
|
-
function sphUnpack64 ( $v )
|
122
|
-
{
|
123
|
-
list($h,$l) = array_values ( unpack ( "N*N*", $v ) );
|
124
|
-
|
125
|
-
// x64 route
|
126
|
-
if ( PHP_INT_SIZE>=8 )
|
127
|
-
{
|
128
|
-
if ( $h<0 ) $h += (1<<32); // because php 5.2.2 to 5.2.5 is totally fucked up again
|
129
|
-
if ( $l<0 ) $l += (1<<32);
|
130
|
-
return ($h<<32) + $l;
|
131
|
-
}
|
132
|
-
|
133
|
-
// x32 route
|
134
|
-
$h = sprintf ( "%u", $h );
|
135
|
-
$l = sprintf ( "%u", $l );
|
136
|
-
$x = "4294967296";
|
137
|
-
|
138
|
-
// bcmath
|
139
|
-
if ( function_exists("bcmul") )
|
140
|
-
return bcadd ( $l, bcmul ( $x, $h ) );
|
141
|
-
|
142
|
-
// no bcmath, 15 or less decimal digits
|
143
|
-
// we can use float, because its actually double and has 52 precision bits
|
144
|
-
if ( $h<1048576 )
|
145
|
-
{
|
146
|
-
$f = ((float)$h)*$x + (float)$l;
|
147
|
-
return sprintf ( "%.0f", $f ); // builtin conversion is only about 39-40 bits precise!
|
148
|
-
}
|
149
|
-
|
150
|
-
// x32 route, 16 or more decimal digits
|
151
|
-
// well, let me know if you *really* need this
|
152
|
-
die ( "INTERNAL ERROR: unpacking more than 15-digit numeric on 32-bit PHP is not implemented yet (contact support)" );
|
153
|
-
}
|
154
|
-
|
155
|
-
|
156
|
-
/// sphinx searchd client class
|
157
|
-
class SphinxClient
|
158
|
-
{
|
159
|
-
var $_host; ///< searchd host (default is "localhost")
|
160
|
-
var $_port; ///< searchd port (default is 3312)
|
161
|
-
var $_offset; ///< how many records to seek from result-set start (default is 0)
|
162
|
-
var $_limit; ///< how many records to return from result-set starting at offset (default is 20)
|
163
|
-
var $_mode; ///< query matching mode (default is SPH_MATCH_ALL)
|
164
|
-
var $_weights; ///< per-field weights (default is 1 for all fields)
|
165
|
-
var $_sort; ///< match sorting mode (default is SPH_SORT_RELEVANCE)
|
166
|
-
var $_sortby; ///< attribute to sort by (defualt is "")
|
167
|
-
var $_min_id; ///< min ID to match (default is 0, which means no limit)
|
168
|
-
var $_max_id; ///< max ID to match (default is 0, which means no limit)
|
169
|
-
var $_filters; ///< search filters
|
170
|
-
var $_groupby; ///< group-by attribute name
|
171
|
-
var $_groupfunc; ///< group-by function (to pre-process group-by attribute value with)
|
172
|
-
var $_groupsort; ///< group-by sorting clause (to sort groups in result set with)
|
173
|
-
var $_groupdistinct;///< group-by count-distinct attribute
|
174
|
-
var $_maxmatches; ///< max matches to retrieve
|
175
|
-
var $_cutoff; ///< cutoff to stop searching at (default is 0)
|
176
|
-
var $_retrycount; ///< distributed retries count
|
177
|
-
var $_retrydelay; ///< distributed retries delay
|
178
|
-
var $_anchor; ///< geographical anchor point
|
179
|
-
var $_indexweights; ///< per-index weights
|
180
|
-
var $_ranker; ///< ranking mode (default is SPH_RANK_PROXIMITY_BM25)
|
181
|
-
var $_maxquerytime; ///< max query time, milliseconds (default is 0, do not limit)
|
182
|
-
var $_fieldweights; ///< per-field-name weights
|
183
|
-
|
184
|
-
var $_error; ///< last error message
|
185
|
-
var $_warning; ///< last warning message
|
186
|
-
|
187
|
-
var $_reqs; ///< requests array for multi-query
|
188
|
-
var $_mbenc; ///< stored mbstring encoding
|
189
|
-
var $_arrayresult; ///< whether $result["matches"] should be a hash or an array
|
190
|
-
var $_timeout; ///< connect timeout
|
191
|
-
|
192
|
-
/////////////////////////////////////////////////////////////////////////////
|
193
|
-
// common stuff
|
194
|
-
/////////////////////////////////////////////////////////////////////////////
|
195
|
-
|
196
|
-
/// create a new client object and fill defaults
|
197
|
-
function SphinxClient ()
|
198
|
-
{
|
199
|
-
// per-client-object settings
|
200
|
-
$this->_host = "localhost";
|
201
|
-
$this->_port = 3312;
|
202
|
-
|
203
|
-
// per-query settings
|
204
|
-
$this->_offset = 0;
|
205
|
-
$this->_limit = 20;
|
206
|
-
$this->_mode = SPH_MATCH_ALL;
|
207
|
-
$this->_weights = array ();
|
208
|
-
$this->_sort = SPH_SORT_RELEVANCE;
|
209
|
-
$this->_sortby = "";
|
210
|
-
$this->_min_id = 0;
|
211
|
-
$this->_max_id = 0;
|
212
|
-
$this->_filters = array ();
|
213
|
-
$this->_groupby = "";
|
214
|
-
$this->_groupfunc = SPH_GROUPBY_DAY;
|
215
|
-
$this->_groupsort = "@group desc";
|
216
|
-
$this->_groupdistinct= "";
|
217
|
-
$this->_maxmatches = 1000;
|
218
|
-
$this->_cutoff = 0;
|
219
|
-
$this->_retrycount = 0;
|
220
|
-
$this->_retrydelay = 0;
|
221
|
-
$this->_anchor = array ();
|
222
|
-
$this->_indexweights= array ();
|
223
|
-
$this->_ranker = SPH_RANK_PROXIMITY_BM25;
|
224
|
-
$this->_maxquerytime= 0;
|
225
|
-
$this->_fieldweights= array();
|
226
|
-
|
227
|
-
$this->_error = ""; // per-reply fields (for single-query case)
|
228
|
-
$this->_warning = "";
|
229
|
-
$this->_reqs = array (); // requests storage (for multi-query case)
|
230
|
-
$this->_mbenc = "";
|
231
|
-
$this->_arrayresult = false;
|
232
|
-
$this->_timeout = 0;
|
233
|
-
}
|
234
|
-
|
235
|
-
/// get last error message (string)
|
236
|
-
function GetLastError ()
|
237
|
-
{
|
238
|
-
return $this->_error;
|
239
|
-
}
|
240
|
-
|
241
|
-
/// get last warning message (string)
|
242
|
-
function GetLastWarning ()
|
243
|
-
{
|
244
|
-
return $this->_warning;
|
245
|
-
}
|
246
|
-
|
247
|
-
/// set searchd host name (string) and port (integer)
|
248
|
-
function SetServer ( $host, $port )
|
249
|
-
{
|
250
|
-
assert ( is_string($host) );
|
251
|
-
assert ( is_int($port) );
|
252
|
-
$this->_host = $host;
|
253
|
-
$this->_port = $port;
|
254
|
-
}
|
255
|
-
|
256
|
-
/// set server connection timeout (0 to remove)
|
257
|
-
function SetConnectTimeout ( $timeout )
|
258
|
-
{
|
259
|
-
assert ( is_numeric($timeout) );
|
260
|
-
$this->_timeout = $timeout;
|
261
|
-
}
|
262
|
-
|
263
|
-
/////////////////////////////////////////////////////////////////////////////
|
264
|
-
|
265
|
-
/// enter mbstring workaround mode
|
266
|
-
function _MBPush ()
|
267
|
-
{
|
268
|
-
$this->_mbenc = "";
|
269
|
-
if ( ini_get ( "mbstring.func_overload" ) & 2 )
|
270
|
-
{
|
271
|
-
$this->_mbenc = mb_internal_encoding();
|
272
|
-
mb_internal_encoding ( "latin1" );
|
273
|
-
}
|
274
|
-
}
|
275
|
-
|
276
|
-
/// leave mbstring workaround mode
|
277
|
-
function _MBPop ()
|
278
|
-
{
|
279
|
-
if ( $this->_mbenc )
|
280
|
-
mb_internal_encoding ( $this->_mbenc );
|
281
|
-
}
|
282
|
-
|
283
|
-
/// connect to searchd server
|
284
|
-
function _Connect ()
|
285
|
-
{
|
286
|
-
$errno = 0;
|
287
|
-
$errstr = "";
|
288
|
-
if ( $this->_timeout<=0 )
|
289
|
-
$fp = @fsockopen ( $this->_host, $this->_port, $errno, $errstr );
|
290
|
-
else
|
291
|
-
$fp = @fsockopen ( $this->_host, $this->_port, $errno, $errstr, $this->_timeout );
|
292
|
-
|
293
|
-
if ( !$fp )
|
294
|
-
{
|
295
|
-
$errstr = trim ( $errstr );
|
296
|
-
$this->_error = "connection to {$this->_host}:{$this->_port} failed (errno=$errno, msg=$errstr)";
|
297
|
-
return false;
|
298
|
-
}
|
299
|
-
|
300
|
-
// check version
|
301
|
-
list(,$v) = unpack ( "N*", fread ( $fp, 4 ) );
|
302
|
-
$v = (int)$v;
|
303
|
-
if ( $v<1 )
|
304
|
-
{
|
305
|
-
fclose ( $fp );
|
306
|
-
$this->_error = "expected searchd protocol version 1+, got version '$v'";
|
307
|
-
return false;
|
308
|
-
}
|
309
|
-
|
310
|
-
// all ok, send my version
|
311
|
-
fwrite ( $fp, pack ( "N", 1 ) );
|
312
|
-
return $fp;
|
313
|
-
}
|
314
|
-
|
315
|
-
/// get and check response packet from searchd server
|
316
|
-
function _GetResponse ( $fp, $client_ver )
|
317
|
-
{
|
318
|
-
$response = "";
|
319
|
-
$len = 0;
|
320
|
-
|
321
|
-
$header = fread ( $fp, 8 );
|
322
|
-
if ( strlen($header)==8 )
|
323
|
-
{
|
324
|
-
list ( $status, $ver, $len ) = array_values ( unpack ( "n2a/Nb", $header ) );
|
325
|
-
$left = $len;
|
326
|
-
while ( $left>0 && !feof($fp) )
|
327
|
-
{
|
328
|
-
$chunk = fread ( $fp, $left );
|
329
|
-
if ( $chunk )
|
330
|
-
{
|
331
|
-
$response .= $chunk;
|
332
|
-
$left -= strlen($chunk);
|
333
|
-
}
|
334
|
-
}
|
335
|
-
}
|
336
|
-
fclose ( $fp );
|
337
|
-
|
338
|
-
// check response
|
339
|
-
$read = strlen ( $response );
|
340
|
-
if ( !$response || $read!=$len )
|
341
|
-
{
|
342
|
-
$this->_error = $len
|
343
|
-
? "failed to read searchd response (status=$status, ver=$ver, len=$len, read=$read)"
|
344
|
-
: "received zero-sized searchd response";
|
345
|
-
return false;
|
346
|
-
}
|
347
|
-
|
348
|
-
// check status
|
349
|
-
if ( $status==SEARCHD_WARNING )
|
350
|
-
{
|
351
|
-
list(,$wlen) = unpack ( "N*", substr ( $response, 0, 4 ) );
|
352
|
-
$this->_warning = substr ( $response, 4, $wlen );
|
353
|
-
return substr ( $response, 4+$wlen );
|
354
|
-
}
|
355
|
-
if ( $status==SEARCHD_ERROR )
|
356
|
-
{
|
357
|
-
$this->_error = "searchd error: " . substr ( $response, 4 );
|
358
|
-
return false;
|
359
|
-
}
|
360
|
-
if ( $status==SEARCHD_RETRY )
|
361
|
-
{
|
362
|
-
$this->_error = "temporary searchd error: " . substr ( $response, 4 );
|
363
|
-
return false;
|
364
|
-
}
|
365
|
-
if ( $status!=SEARCHD_OK )
|
366
|
-
{
|
367
|
-
$this->_error = "unknown status code '$status'";
|
368
|
-
return false;
|
369
|
-
}
|
370
|
-
|
371
|
-
// check version
|
372
|
-
if ( $ver<$client_ver )
|
373
|
-
{
|
374
|
-
$this->_warning = sprintf ( "searchd command v.%d.%d older than client's v.%d.%d, some options might not work",
|
375
|
-
$ver>>8, $ver&0xff, $client_ver>>8, $client_ver&0xff );
|
376
|
-
}
|
377
|
-
|
378
|
-
return $response;
|
379
|
-
}
|
380
|
-
|
381
|
-
/////////////////////////////////////////////////////////////////////////////
|
382
|
-
// searching
|
383
|
-
/////////////////////////////////////////////////////////////////////////////
|
384
|
-
|
385
|
-
/// set offset and count into result set,
|
386
|
-
/// and optionally set max-matches and cutoff limits
|
387
|
-
function SetLimits ( $offset, $limit, $max=0, $cutoff=0 )
|
388
|
-
{
|
389
|
-
assert ( is_int($offset) );
|
390
|
-
assert ( is_int($limit) );
|
391
|
-
assert ( $offset>=0 );
|
392
|
-
assert ( $limit>0 );
|
393
|
-
assert ( $max>=0 );
|
394
|
-
$this->_offset = $offset;
|
395
|
-
$this->_limit = $limit;
|
396
|
-
if ( $max>0 )
|
397
|
-
$this->_maxmatches = $max;
|
398
|
-
if ( $cutoff>0 )
|
399
|
-
$this->_cutoff = $cutoff;
|
400
|
-
}
|
401
|
-
|
402
|
-
/// set maximum query time, in milliseconds, per-index
|
403
|
-
/// integer, 0 means "do not limit"
|
404
|
-
function SetMaxQueryTime ( $max )
|
405
|
-
{
|
406
|
-
assert ( is_int($max) );
|
407
|
-
assert ( $max>=0 );
|
408
|
-
$this->_maxquerytime = $max;
|
409
|
-
}
|
410
|
-
|
411
|
-
/// set matching mode
|
412
|
-
function SetMatchMode ( $mode )
|
413
|
-
{
|
414
|
-
assert ( $mode==SPH_MATCH_ALL
|
415
|
-
|| $mode==SPH_MATCH_ANY
|
416
|
-
|| $mode==SPH_MATCH_PHRASE
|
417
|
-
|| $mode==SPH_MATCH_BOOLEAN
|
418
|
-
|| $mode==SPH_MATCH_EXTENDED
|
419
|
-
|| $mode==SPH_MATCH_FULLSCAN
|
420
|
-
|| $mode==SPH_MATCH_EXTENDED2 );
|
421
|
-
$this->_mode = $mode;
|
422
|
-
}
|
423
|
-
|
424
|
-
/// set ranking mode
|
425
|
-
function SetRankingMode ( $ranker )
|
426
|
-
{
|
427
|
-
assert ( $ranker==SPH_RANK_PROXIMITY_BM25
|
428
|
-
|| $ranker==SPH_RANK_BM25
|
429
|
-
|| $ranker==SPH_RANK_NONE
|
430
|
-
|| $ranker==SPH_RANK_WORDCOUNT );
|
431
|
-
$this->_ranker = $ranker;
|
432
|
-
}
|
433
|
-
|
434
|
-
/// set matches sorting mode
|
435
|
-
function SetSortMode ( $mode, $sortby="" )
|
436
|
-
{
|
437
|
-
assert (
|
438
|
-
$mode==SPH_SORT_RELEVANCE ||
|
439
|
-
$mode==SPH_SORT_ATTR_DESC ||
|
440
|
-
$mode==SPH_SORT_ATTR_ASC ||
|
441
|
-
$mode==SPH_SORT_TIME_SEGMENTS ||
|
442
|
-
$mode==SPH_SORT_EXTENDED ||
|
443
|
-
$mode==SPH_SORT_EXPR );
|
444
|
-
assert ( is_string($sortby) );
|
445
|
-
assert ( $mode==SPH_SORT_RELEVANCE || strlen($sortby)>0 );
|
446
|
-
|
447
|
-
$this->_sort = $mode;
|
448
|
-
$this->_sortby = $sortby;
|
449
|
-
}
|
450
|
-
|
451
|
-
/// bind per-field weights by order
|
452
|
-
/// DEPRECATED; use SetFieldWeights() instead
|
453
|
-
function SetWeights ( $weights )
|
454
|
-
{
|
455
|
-
assert ( is_array($weights) );
|
456
|
-
foreach ( $weights as $weight )
|
457
|
-
assert ( is_int($weight) );
|
458
|
-
|
459
|
-
$this->_weights = $weights;
|
460
|
-
}
|
461
|
-
|
462
|
-
/// bind per-field weights by name
|
463
|
-
function SetFieldWeights ( $weights )
|
464
|
-
{
|
465
|
-
assert ( is_array($weights) );
|
466
|
-
foreach ( $weights as $name=>$weight )
|
467
|
-
{
|
468
|
-
assert ( is_string($name) );
|
469
|
-
assert ( is_int($weight) );
|
470
|
-
}
|
471
|
-
$this->_fieldweights = $weights;
|
472
|
-
}
|
473
|
-
|
474
|
-
/// bind per-index weights by name
|
475
|
-
function SetIndexWeights ( $weights )
|
476
|
-
{
|
477
|
-
assert ( is_array($weights) );
|
478
|
-
foreach ( $weights as $index=>$weight )
|
479
|
-
{
|
480
|
-
assert ( is_string($index) );
|
481
|
-
assert ( is_int($weight) );
|
482
|
-
}
|
483
|
-
$this->_indexweights = $weights;
|
484
|
-
}
|
485
|
-
|
486
|
-
/// set IDs range to match
|
487
|
-
/// only match records if document ID is beetwen $min and $max (inclusive)
|
488
|
-
function SetIDRange ( $min, $max )
|
489
|
-
{
|
490
|
-
assert ( is_numeric($min) );
|
491
|
-
assert ( is_numeric($max) );
|
492
|
-
assert ( $min<=$max );
|
493
|
-
$this->_min_id = $min;
|
494
|
-
$this->_max_id = $max;
|
495
|
-
}
|
496
|
-
|
497
|
-
/// set values set filter
|
498
|
-
/// only match records where $attribute value is in given set
|
499
|
-
function SetFilter ( $attribute, $values, $exclude=false )
|
500
|
-
{
|
501
|
-
assert ( is_string($attribute) );
|
502
|
-
assert ( is_array($values) );
|
503
|
-
assert ( count($values) );
|
504
|
-
|
505
|
-
if ( is_array($values) && count($values) )
|
506
|
-
{
|
507
|
-
foreach ( $values as $value )
|
508
|
-
assert ( is_numeric($value) );
|
509
|
-
|
510
|
-
$this->_filters[] = array ( "type"=>SPH_FILTER_VALUES, "attr"=>$attribute, "exclude"=>$exclude, "values"=>$values );
|
511
|
-
}
|
512
|
-
}
|
513
|
-
|
514
|
-
/// set range filter
|
515
|
-
/// only match records if $attribute value is beetwen $min and $max (inclusive)
|
516
|
-
function SetFilterRange ( $attribute, $min, $max, $exclude=false )
|
517
|
-
{
|
518
|
-
assert ( is_string($attribute) );
|
519
|
-
assert ( is_int($min) );
|
520
|
-
assert ( is_int($max) );
|
521
|
-
assert ( $min<=$max );
|
522
|
-
|
523
|
-
$this->_filters[] = array ( "type"=>SPH_FILTER_RANGE, "attr"=>$attribute, "exclude"=>$exclude, "min"=>$min, "max"=>$max );
|
524
|
-
}
|
525
|
-
|
526
|
-
/// set float range filter
|
527
|
-
/// only match records if $attribute value is beetwen $min and $max (inclusive)
|
528
|
-
function SetFilterFloatRange ( $attribute, $min, $max, $exclude=false )
|
529
|
-
{
|
530
|
-
assert ( is_string($attribute) );
|
531
|
-
assert ( is_float($min) );
|
532
|
-
assert ( is_float($max) );
|
533
|
-
assert ( $min<=$max );
|
534
|
-
|
535
|
-
$this->_filters[] = array ( "type"=>SPH_FILTER_FLOATRANGE, "attr"=>$attribute, "exclude"=>$exclude, "min"=>$min, "max"=>$max );
|
536
|
-
}
|
537
|
-
|
538
|
-
/// setup anchor point for geosphere distance calculations
|
539
|
-
/// required to use @geodist in filters and sorting
|
540
|
-
/// latitude and longitude must be in radians
|
541
|
-
function SetGeoAnchor ( $attrlat, $attrlong, $lat, $long )
|
542
|
-
{
|
543
|
-
assert ( is_string($attrlat) );
|
544
|
-
assert ( is_string($attrlong) );
|
545
|
-
assert ( is_float($lat) );
|
546
|
-
assert ( is_float($long) );
|
547
|
-
|
548
|
-
$this->_anchor = array ( "attrlat"=>$attrlat, "attrlong"=>$attrlong, "lat"=>$lat, "long"=>$long );
|
549
|
-
}
|
550
|
-
|
551
|
-
/// set grouping attribute and function
|
552
|
-
function SetGroupBy ( $attribute, $func, $groupsort="@group desc" )
|
553
|
-
{
|
554
|
-
assert ( is_string($attribute) );
|
555
|
-
assert ( is_string($groupsort) );
|
556
|
-
assert ( $func==SPH_GROUPBY_DAY
|
557
|
-
|| $func==SPH_GROUPBY_WEEK
|
558
|
-
|| $func==SPH_GROUPBY_MONTH
|
559
|
-
|| $func==SPH_GROUPBY_YEAR
|
560
|
-
|| $func==SPH_GROUPBY_ATTR
|
561
|
-
|| $func==SPH_GROUPBY_ATTRPAIR );
|
562
|
-
|
563
|
-
$this->_groupby = $attribute;
|
564
|
-
$this->_groupfunc = $func;
|
565
|
-
$this->_groupsort = $groupsort;
|
566
|
-
}
|
567
|
-
|
568
|
-
/// set count-distinct attribute for group-by queries
|
569
|
-
function SetGroupDistinct ( $attribute )
|
570
|
-
{
|
571
|
-
assert ( is_string($attribute) );
|
572
|
-
$this->_groupdistinct = $attribute;
|
573
|
-
}
|
574
|
-
|
575
|
-
/// set distributed retries count and delay
|
576
|
-
function SetRetries ( $count, $delay=0 )
|
577
|
-
{
|
578
|
-
assert ( is_int($count) && $count>=0 );
|
579
|
-
assert ( is_int($delay) && $delay>=0 );
|
580
|
-
$this->_retrycount = $count;
|
581
|
-
$this->_retrydelay = $delay;
|
582
|
-
}
|
583
|
-
|
584
|
-
/// set result set format (hash or array; hash by default)
|
585
|
-
/// PHP specific; needed for group-by-MVA result sets that may contain duplicate IDs
|
586
|
-
function SetArrayResult ( $arrayresult )
|
587
|
-
{
|
588
|
-
assert ( is_bool($arrayresult) );
|
589
|
-
$this->_arrayresult = $arrayresult;
|
590
|
-
}
|
591
|
-
|
592
|
-
//////////////////////////////////////////////////////////////////////////////
|
593
|
-
|
594
|
-
/// clear all filters (for multi-queries)
|
595
|
-
function ResetFilters ()
|
596
|
-
{
|
597
|
-
$this->_filters = array();
|
598
|
-
$this->_anchor = array();
|
599
|
-
}
|
600
|
-
|
601
|
-
/// clear groupby settings (for multi-queries)
|
602
|
-
function ResetGroupBy ()
|
603
|
-
{
|
604
|
-
$this->_groupby = "";
|
605
|
-
$this->_groupfunc = SPH_GROUPBY_DAY;
|
606
|
-
$this->_groupsort = "@group desc";
|
607
|
-
$this->_groupdistinct= "";
|
608
|
-
}
|
609
|
-
|
610
|
-
//////////////////////////////////////////////////////////////////////////////
|
611
|
-
|
612
|
-
/// connect to searchd server, run given search query through given indexes,
|
613
|
-
/// and return the search results
|
614
|
-
function Query ( $query, $index="*", $comment="" )
|
615
|
-
{
|
616
|
-
assert ( empty($this->_reqs) );
|
617
|
-
|
618
|
-
$this->AddQuery ( $query, $index, $comment );
|
619
|
-
$results = $this->RunQueries ();
|
620
|
-
$this->_reqs = array (); // just in case it failed too early
|
621
|
-
|
622
|
-
if ( !is_array($results) )
|
623
|
-
return false; // probably network error; error message should be already filled
|
624
|
-
|
625
|
-
$this->_error = $results[0]["error"];
|
626
|
-
$this->_warning = $results[0]["warning"];
|
627
|
-
if ( $results[0]["status"]==SEARCHD_ERROR )
|
628
|
-
return false;
|
629
|
-
else
|
630
|
-
return $results[0];
|
631
|
-
}
|
632
|
-
|
633
|
-
/// helper to pack floats in network byte order
|
634
|
-
function _PackFloat ( $f )
|
635
|
-
{
|
636
|
-
$t1 = pack ( "f", $f ); // machine order
|
637
|
-
list(,$t2) = unpack ( "L*", $t1 ); // int in machine order
|
638
|
-
return pack ( "N", $t2 );
|
639
|
-
}
|
640
|
-
|
641
|
-
/// add query to multi-query batch
|
642
|
-
/// returns index into results array from RunQueries() call
|
643
|
-
function AddQuery ( $query, $index="*", $comment="" )
|
644
|
-
{
|
645
|
-
// mbstring workaround
|
646
|
-
$this->_MBPush ();
|
647
|
-
|
648
|
-
// build request
|
649
|
-
$req = pack ( "NNNNN", $this->_offset, $this->_limit, $this->_mode, $this->_ranker, $this->_sort ); // mode and limits
|
650
|
-
$req .= pack ( "N", strlen($this->_sortby) ) . $this->_sortby;
|
651
|
-
$req .= pack ( "N", strlen($query) ) . $query; // query itself
|
652
|
-
$req .= pack ( "N", count($this->_weights) ); // weights
|
653
|
-
foreach ( $this->_weights as $weight )
|
654
|
-
$req .= pack ( "N", (int)$weight );
|
655
|
-
$req .= pack ( "N", strlen($index) ) . $index; // indexes
|
656
|
-
$req .= pack ( "N", 1 ); // id64 range marker
|
657
|
-
$req .= sphPack64 ( $this->_min_id ) . sphPack64 ( $this->_max_id ); // id64 range
|
658
|
-
|
659
|
-
// filters
|
660
|
-
$req .= pack ( "N", count($this->_filters) );
|
661
|
-
foreach ( $this->_filters as $filter )
|
662
|
-
{
|
663
|
-
$req .= pack ( "N", strlen($filter["attr"]) ) . $filter["attr"];
|
664
|
-
$req .= pack ( "N", $filter["type"] );
|
665
|
-
switch ( $filter["type"] )
|
666
|
-
{
|
667
|
-
case SPH_FILTER_VALUES:
|
668
|
-
$req .= pack ( "N", count($filter["values"]) );
|
669
|
-
foreach ( $filter["values"] as $value )
|
670
|
-
$req .= pack ( "N", floatval($value) ); // this uberhack is to workaround 32bit signed int limit on x32 platforms
|
671
|
-
break;
|
672
|
-
|
673
|
-
case SPH_FILTER_RANGE:
|
674
|
-
$req .= pack ( "NN", $filter["min"], $filter["max"] );
|
675
|
-
break;
|
676
|
-
|
677
|
-
case SPH_FILTER_FLOATRANGE:
|
678
|
-
$req .= $this->_PackFloat ( $filter["min"] ) . $this->_PackFloat ( $filter["max"] );
|
679
|
-
break;
|
680
|
-
|
681
|
-
default:
|
682
|
-
assert ( 0 && "internal error: unhandled filter type" );
|
683
|
-
}
|
684
|
-
$req .= pack ( "N", $filter["exclude"] );
|
685
|
-
}
|
686
|
-
|
687
|
-
// group-by clause, max-matches count, group-sort clause, cutoff count
|
688
|
-
$req .= pack ( "NN", $this->_groupfunc, strlen($this->_groupby) ) . $this->_groupby;
|
689
|
-
$req .= pack ( "N", $this->_maxmatches );
|
690
|
-
$req .= pack ( "N", strlen($this->_groupsort) ) . $this->_groupsort;
|
691
|
-
$req .= pack ( "NNN", $this->_cutoff, $this->_retrycount, $this->_retrydelay );
|
692
|
-
$req .= pack ( "N", strlen($this->_groupdistinct) ) . $this->_groupdistinct;
|
693
|
-
|
694
|
-
// anchor point
|
695
|
-
if ( empty($this->_anchor) )
|
696
|
-
{
|
697
|
-
$req .= pack ( "N", 0 );
|
698
|
-
} else
|
699
|
-
{
|
700
|
-
$a =& $this->_anchor;
|
701
|
-
$req .= pack ( "N", 1 );
|
702
|
-
$req .= pack ( "N", strlen($a["attrlat"]) ) . $a["attrlat"];
|
703
|
-
$req .= pack ( "N", strlen($a["attrlong"]) ) . $a["attrlong"];
|
704
|
-
$req .= $this->_PackFloat ( $a["lat"] ) . $this->_PackFloat ( $a["long"] );
|
705
|
-
}
|
706
|
-
|
707
|
-
// per-index weights
|
708
|
-
$req .= pack ( "N", count($this->_indexweights) );
|
709
|
-
foreach ( $this->_indexweights as $idx=>$weight )
|
710
|
-
$req .= pack ( "N", strlen($idx) ) . $idx . pack ( "N", $weight );
|
711
|
-
|
712
|
-
// max query time
|
713
|
-
$req .= pack ( "N", $this->_maxquerytime );
|
714
|
-
|
715
|
-
// per-field weights
|
716
|
-
$req .= pack ( "N", count($this->_fieldweights) );
|
717
|
-
foreach ( $this->_fieldweights as $field=>$weight )
|
718
|
-
$req .= pack ( "N", strlen($field) ) . $field . pack ( "N", $weight );
|
719
|
-
|
720
|
-
// comment
|
721
|
-
$req .= pack ( "N", strlen($comment) ) . $comment;
|
722
|
-
|
723
|
-
// mbstring workaround
|
724
|
-
$this->_MBPop ();
|
725
|
-
|
726
|
-
// store request to requests array
|
727
|
-
$this->_reqs[] = $req;
|
728
|
-
return count($this->_reqs)-1;
|
729
|
-
}
|
730
|
-
|
731
|
-
/// connect to searchd, run queries batch, and return an array of result sets
|
732
|
-
function RunQueries ()
|
733
|
-
{
|
734
|
-
if ( empty($this->_reqs) )
|
735
|
-
{
|
736
|
-
$this->_error = "no queries defined, issue AddQuery() first";
|
737
|
-
return false;
|
738
|
-
}
|
739
|
-
|
740
|
-
// mbstring workaround
|
741
|
-
$this->_MBPush ();
|
742
|
-
|
743
|
-
if (!( $fp = $this->_Connect() ))
|
744
|
-
{
|
745
|
-
$this->_MBPop ();
|
746
|
-
return false;
|
747
|
-
}
|
748
|
-
|
749
|
-
////////////////////////////
|
750
|
-
// send query, get response
|
751
|
-
////////////////////////////
|
752
|
-
|
753
|
-
$nreqs = count($this->_reqs);
|
754
|
-
$req = join ( "", $this->_reqs );
|
755
|
-
$len = 4+strlen($req);
|
756
|
-
$req = pack ( "nnNN", SEARCHD_COMMAND_SEARCH, VER_COMMAND_SEARCH, $len, $nreqs ) . $req; // add header
|
757
|
-
|
758
|
-
fwrite ( $fp, $req, $len+8 );
|
759
|
-
if (!( $response = $this->_GetResponse ( $fp, VER_COMMAND_SEARCH ) ))
|
760
|
-
{
|
761
|
-
$this->_MBPop ();
|
762
|
-
return false;
|
763
|
-
}
|
764
|
-
|
765
|
-
$this->_reqs = array ();
|
766
|
-
|
767
|
-
//////////////////
|
768
|
-
// parse response
|
769
|
-
//////////////////
|
770
|
-
|
771
|
-
$p = 0; // current position
|
772
|
-
$max = strlen($response); // max position for checks, to protect against broken responses
|
773
|
-
|
774
|
-
$results = array ();
|
775
|
-
for ( $ires=0; $ires<$nreqs && $p<$max; $ires++ )
|
776
|
-
{
|
777
|
-
$results[] = array();
|
778
|
-
$result =& $results[$ires];
|
779
|
-
|
780
|
-
$result["error"] = "";
|
781
|
-
$result["warning"] = "";
|
782
|
-
|
783
|
-
// extract status
|
784
|
-
list(,$status) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
|
785
|
-
$result["status"] = $status;
|
786
|
-
if ( $status!=SEARCHD_OK )
|
787
|
-
{
|
788
|
-
list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
|
789
|
-
$message = substr ( $response, $p, $len ); $p += $len;
|
790
|
-
|
791
|
-
if ( $status==SEARCHD_WARNING )
|
792
|
-
{
|
793
|
-
$result["warning"] = $message;
|
794
|
-
} else
|
795
|
-
{
|
796
|
-
$result["error"] = $message;
|
797
|
-
continue;
|
798
|
-
}
|
799
|
-
}
|
800
|
-
|
801
|
-
// read schema
|
802
|
-
$fields = array ();
|
803
|
-
$attrs = array ();
|
804
|
-
|
805
|
-
list(,$nfields) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
|
806
|
-
while ( $nfields-->0 && $p<$max )
|
807
|
-
{
|
808
|
-
list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
|
809
|
-
$fields[] = substr ( $response, $p, $len ); $p += $len;
|
810
|
-
}
|
811
|
-
$result["fields"] = $fields;
|
812
|
-
|
813
|
-
list(,$nattrs) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
|
814
|
-
while ( $nattrs-->0 && $p<$max )
|
815
|
-
{
|
816
|
-
list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
|
817
|
-
$attr = substr ( $response, $p, $len ); $p += $len;
|
818
|
-
list(,$type) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
|
819
|
-
$attrs[$attr] = $type;
|
820
|
-
}
|
821
|
-
$result["attrs"] = $attrs;
|
822
|
-
|
823
|
-
// read match count
|
824
|
-
list(,$count) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
|
825
|
-
list(,$id64) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
|
826
|
-
|
827
|
-
// read matches
|
828
|
-
$idx = -1;
|
829
|
-
while ( $count-->0 && $p<$max )
|
830
|
-
{
|
831
|
-
// index into result array
|
832
|
-
$idx++;
|
833
|
-
|
834
|
-
// parse document id and weight
|
835
|
-
if ( $id64 )
|
836
|
-
{
|
837
|
-
$doc = sphUnpack64 ( substr ( $response, $p, 8 ) ); $p += 8;
|
838
|
-
list(,$weight) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
|
839
|
-
} else
|
840
|
-
{
|
841
|
-
list ( $doc, $weight ) = array_values ( unpack ( "N*N*",
|
842
|
-
substr ( $response, $p, 8 ) ) );
|
843
|
-
$p += 8;
|
844
|
-
|
845
|
-
if ( PHP_INT_SIZE>=8 )
|
846
|
-
{
|
847
|
-
// x64 route, workaround broken unpack() in 5.2.2+
|
848
|
-
if ( $doc<0 ) $doc += (1<<32);
|
849
|
-
} else
|
850
|
-
{
|
851
|
-
// x32 route, workaround php signed/unsigned braindamage
|
852
|
-
$doc = sprintf ( "%u", $doc );
|
853
|
-
}
|
854
|
-
}
|
855
|
-
$weight = sprintf ( "%u", $weight );
|
856
|
-
|
857
|
-
// create match entry
|
858
|
-
if ( $this->_arrayresult )
|
859
|
-
$result["matches"][$idx] = array ( "id"=>$doc, "weight"=>$weight );
|
860
|
-
else
|
861
|
-
$result["matches"][$doc]["weight"] = $weight;
|
862
|
-
|
863
|
-
// parse and create attributes
|
864
|
-
$attrvals = array ();
|
865
|
-
foreach ( $attrs as $attr=>$type )
|
866
|
-
{
|
867
|
-
// handle floats
|
868
|
-
if ( $type==SPH_ATTR_FLOAT )
|
869
|
-
{
|
870
|
-
list(,$uval) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
|
871
|
-
list(,$fval) = unpack ( "f*", pack ( "L", $uval ) );
|
872
|
-
$attrvals[$attr] = $fval;
|
873
|
-
continue;
|
874
|
-
}
|
875
|
-
|
876
|
-
// handle everything else as unsigned ints
|
877
|
-
list(,$val) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
|
878
|
-
if ( $type & SPH_ATTR_MULTI )
|
879
|
-
{
|
880
|
-
$attrvals[$attr] = array ();
|
881
|
-
$nvalues = $val;
|
882
|
-
while ( $nvalues-->0 && $p<$max )
|
883
|
-
{
|
884
|
-
list(,$val) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
|
885
|
-
$attrvals[$attr][] = sprintf ( "%u", $val );
|
886
|
-
}
|
887
|
-
} else
|
888
|
-
{
|
889
|
-
$attrvals[$attr] = sprintf ( "%u", $val );
|
890
|
-
}
|
891
|
-
}
|
892
|
-
|
893
|
-
if ( $this->_arrayresult )
|
894
|
-
$result["matches"][$idx]["attrs"] = $attrvals;
|
895
|
-
else
|
896
|
-
$result["matches"][$doc]["attrs"] = $attrvals;
|
897
|
-
}
|
898
|
-
|
899
|
-
list ( $total, $total_found, $msecs, $words ) =
|
900
|
-
array_values ( unpack ( "N*N*N*N*", substr ( $response, $p, 16 ) ) );
|
901
|
-
$result["total"] = sprintf ( "%u", $total );
|
902
|
-
$result["total_found"] = sprintf ( "%u", $total_found );
|
903
|
-
$result["time"] = sprintf ( "%.3f", $msecs/1000 );
|
904
|
-
$p += 16;
|
905
|
-
|
906
|
-
while ( $words-->0 && $p<$max )
|
907
|
-
{
|
908
|
-
list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
|
909
|
-
$word = substr ( $response, $p, $len ); $p += $len;
|
910
|
-
list ( $docs, $hits ) = array_values ( unpack ( "N*N*", substr ( $response, $p, 8 ) ) ); $p += 8;
|
911
|
-
$result["words"][$word] = array (
|
912
|
-
"docs"=>sprintf ( "%u", $docs ),
|
913
|
-
"hits"=>sprintf ( "%u", $hits ) );
|
914
|
-
}
|
915
|
-
}
|
916
|
-
|
917
|
-
$this->_MBPop ();
|
918
|
-
return $results;
|
919
|
-
}
|
920
|
-
|
921
|
-
/////////////////////////////////////////////////////////////////////////////
|
922
|
-
// excerpts generation
|
923
|
-
/////////////////////////////////////////////////////////////////////////////
|
924
|
-
|
925
|
-
/// connect to searchd server, and generate exceprts (snippets)
|
926
|
-
/// of given documents for given query. returns false on failure,
|
927
|
-
/// an array of snippets on success
|
928
|
-
function BuildExcerpts ( $docs, $index, $words, $opts=array() )
|
929
|
-
{
|
930
|
-
assert ( is_array($docs) );
|
931
|
-
assert ( is_string($index) );
|
932
|
-
assert ( is_string($words) );
|
933
|
-
assert ( is_array($opts) );
|
934
|
-
|
935
|
-
$this->_MBPush ();
|
936
|
-
|
937
|
-
if (!( $fp = $this->_Connect() ))
|
938
|
-
{
|
939
|
-
$this->_MBPop();
|
940
|
-
return false;
|
941
|
-
}
|
942
|
-
|
943
|
-
/////////////////
|
944
|
-
// fixup options
|
945
|
-
/////////////////
|
946
|
-
|
947
|
-
if ( !isset($opts["before_match"]) ) $opts["before_match"] = "<b>";
|
948
|
-
if ( !isset($opts["after_match"]) ) $opts["after_match"] = "</b>";
|
949
|
-
if ( !isset($opts["chunk_separator"]) ) $opts["chunk_separator"] = " ... ";
|
950
|
-
if ( !isset($opts["limit"]) ) $opts["limit"] = 256;
|
951
|
-
if ( !isset($opts["around"]) ) $opts["around"] = 5;
|
952
|
-
if ( !isset($opts["exact_phrase"]) ) $opts["exact_phrase"] = false;
|
953
|
-
if ( !isset($opts["single_passage"]) ) $opts["single_passage"] = false;
|
954
|
-
if ( !isset($opts["use_boundaries"]) ) $opts["use_boundaries"] = false;
|
955
|
-
if ( !isset($opts["weight_order"]) ) $opts["weight_order"] = false;
|
956
|
-
|
957
|
-
/////////////////
|
958
|
-
// build request
|
959
|
-
/////////////////
|
960
|
-
|
961
|
-
// v.1.0 req
|
962
|
-
$flags = 1; // remove spaces
|
963
|
-
if ( $opts["exact_phrase"] ) $flags |= 2;
|
964
|
-
if ( $opts["single_passage"] ) $flags |= 4;
|
965
|
-
if ( $opts["use_boundaries"] ) $flags |= 8;
|
966
|
-
if ( $opts["weight_order"] ) $flags |= 16;
|
967
|
-
$req = pack ( "NN", 0, $flags ); // mode=0, flags=$flags
|
968
|
-
$req .= pack ( "N", strlen($index) ) . $index; // req index
|
969
|
-
$req .= pack ( "N", strlen($words) ) . $words; // req words
|
970
|
-
|
971
|
-
// options
|
972
|
-
$req .= pack ( "N", strlen($opts["before_match"]) ) . $opts["before_match"];
|
973
|
-
$req .= pack ( "N", strlen($opts["after_match"]) ) . $opts["after_match"];
|
974
|
-
$req .= pack ( "N", strlen($opts["chunk_separator"]) ) . $opts["chunk_separator"];
|
975
|
-
$req .= pack ( "N", (int)$opts["limit"] );
|
976
|
-
$req .= pack ( "N", (int)$opts["around"] );
|
977
|
-
|
978
|
-
// documents
|
979
|
-
$req .= pack ( "N", count($docs) );
|
980
|
-
foreach ( $docs as $doc )
|
981
|
-
{
|
982
|
-
assert ( is_string($doc) );
|
983
|
-
$req .= pack ( "N", strlen($doc) ) . $doc;
|
984
|
-
}
|
985
|
-
|
986
|
-
////////////////////////////
|
987
|
-
// send query, get response
|
988
|
-
////////////////////////////
|
989
|
-
|
990
|
-
$len = strlen($req);
|
991
|
-
$req = pack ( "nnN", SEARCHD_COMMAND_EXCERPT, VER_COMMAND_EXCERPT, $len ) . $req; // add header
|
992
|
-
$wrote = fwrite ( $fp, $req, $len+8 );
|
993
|
-
if (!( $response = $this->_GetResponse ( $fp, VER_COMMAND_EXCERPT ) ))
|
994
|
-
{
|
995
|
-
$this->_MBPop ();
|
996
|
-
return false;
|
997
|
-
}
|
998
|
-
|
999
|
-
//////////////////
|
1000
|
-
// parse response
|
1001
|
-
//////////////////
|
1002
|
-
|
1003
|
-
$pos = 0;
|
1004
|
-
$res = array ();
|
1005
|
-
$rlen = strlen($response);
|
1006
|
-
for ( $i=0; $i<count($docs); $i++ )
|
1007
|
-
{
|
1008
|
-
list(,$len) = unpack ( "N*", substr ( $response, $pos, 4 ) );
|
1009
|
-
$pos += 4;
|
1010
|
-
|
1011
|
-
if ( $pos+$len > $rlen )
|
1012
|
-
{
|
1013
|
-
$this->_error = "incomplete reply";
|
1014
|
-
$this->_MBPop ();
|
1015
|
-
return false;
|
1016
|
-
}
|
1017
|
-
$res[] = $len ? substr ( $response, $pos, $len ) : "";
|
1018
|
-
$pos += $len;
|
1019
|
-
}
|
1020
|
-
|
1021
|
-
$this->_MBPop ();
|
1022
|
-
return $res;
|
1023
|
-
}
|
1024
|
-
|
1025
|
-
|
1026
|
-
/////////////////////////////////////////////////////////////////////////////
|
1027
|
-
// keyword generation
|
1028
|
-
/////////////////////////////////////////////////////////////////////////////
|
1029
|
-
|
1030
|
-
/// connect to searchd server, and generate keyword list for a given query
|
1031
|
-
/// returns false on failure,
|
1032
|
-
/// an array of words on success
|
1033
|
-
function BuildKeywords ( $query, $index, $hits )
|
1034
|
-
{
|
1035
|
-
assert ( is_string($query) );
|
1036
|
-
assert ( is_string($index) );
|
1037
|
-
assert ( is_bool($hits) );
|
1038
|
-
|
1039
|
-
// Commented out for testing Riddle
|
1040
|
-
// $this->_MBPush ();
|
1041
|
-
//
|
1042
|
-
// if (!( $fp = $this->_Connect() ))
|
1043
|
-
// {
|
1044
|
-
// $this->_MBPop();
|
1045
|
-
// return false;
|
1046
|
-
// }
|
1047
|
-
|
1048
|
-
/////////////////
|
1049
|
-
// build request
|
1050
|
-
/////////////////
|
1051
|
-
|
1052
|
-
// v.1.0 req
|
1053
|
-
$req = pack ( "N", strlen($query) ) . $query; // req query
|
1054
|
-
$req .= pack ( "N", strlen($index) ) . $index; // req index
|
1055
|
-
$req .= pack ( "N", (int)$hits );
|
1056
|
-
|
1057
|
-
// Line for testing Riddle:
|
1058
|
-
return $req;
|
1059
|
-
|
1060
|
-
////////////////////////////
|
1061
|
-
// send query, get response
|
1062
|
-
////////////////////////////
|
1063
|
-
|
1064
|
-
$len = strlen($req);
|
1065
|
-
$req = pack ( "nnN", SEARCHD_COMMAND_KEYWORDS, VER_COMMAND_KEYWORDS, $len ) . $req; // add header
|
1066
|
-
$wrote = fwrite ( $fp, $req, $len+8 );
|
1067
|
-
if (!( $response = $this->_GetResponse ( $fp, VER_COMMAND_KEYWORDS ) ))
|
1068
|
-
{
|
1069
|
-
$this->_MBPop ();
|
1070
|
-
return false;
|
1071
|
-
}
|
1072
|
-
|
1073
|
-
//////////////////
|
1074
|
-
// parse response
|
1075
|
-
//////////////////
|
1076
|
-
|
1077
|
-
$pos = 0;
|
1078
|
-
$res = array ();
|
1079
|
-
$rlen = strlen($response);
|
1080
|
-
list(,$nwords) = unpack ( "N*", substr ( $response, $pos, 4 ) );
|
1081
|
-
$pos += 4;
|
1082
|
-
for ( $i=0; $i<$nwords; $i++ )
|
1083
|
-
{
|
1084
|
-
list(,$len) = unpack ( "N*", substr ( $response, $pos, 4 ) ); $pos += 4;
|
1085
|
-
$tokenized = $len ? substr ( $response, $pos, $len ) : "";
|
1086
|
-
$pos += $len;
|
1087
|
-
|
1088
|
-
list(,$len) = unpack ( "N*", substr ( $response, $pos, 4 ) ); $pos += 4;
|
1089
|
-
$normalized = $len ? substr ( $response, $pos, $len ) : "";
|
1090
|
-
$pos += $len;
|
1091
|
-
|
1092
|
-
$res[] = array ( "tokenized"=>$tokenized, "normalized"=>$normalized );
|
1093
|
-
|
1094
|
-
if ( $hits )
|
1095
|
-
{
|
1096
|
-
list($ndocs,$nhits) = array_values ( unpack ( "N*N*", substr ( $response, $pos, 8 ) ) );
|
1097
|
-
$pos += 8;
|
1098
|
-
$res [$i]["docs"] = $ndocs;
|
1099
|
-
$res [$i]["hits"] = $nhits;
|
1100
|
-
}
|
1101
|
-
|
1102
|
-
if ( $pos > $rlen )
|
1103
|
-
{
|
1104
|
-
$this->_error = "incomplete reply";
|
1105
|
-
$this->_MBPop ();
|
1106
|
-
return false;
|
1107
|
-
}
|
1108
|
-
}
|
1109
|
-
|
1110
|
-
$this->_MBPop ();
|
1111
|
-
return $res;
|
1112
|
-
}
|
1113
|
-
|
1114
|
-
function EscapeString ( $string )
|
1115
|
-
{
|
1116
|
-
$from = array ( '(',')','|','-','!','@','~','"','&', '/' );
|
1117
|
-
$to = array ( '\(','\)','\|','\-','\!','\@','\~','\"', '\&', '\/' );
|
1118
|
-
|
1119
|
-
return str_replace ( $from, $to, $string );
|
1120
|
-
}
|
1121
|
-
|
1122
|
-
/////////////////////////////////////////////////////////////////////////////
|
1123
|
-
// attribute updates
|
1124
|
-
/////////////////////////////////////////////////////////////////////////////
|
1125
|
-
|
1126
|
-
/// update given attribute values on given documents in given indexes
|
1127
|
-
/// returns amount of updated documents (0 or more) on success, or -1 on failure
|
1128
|
-
function UpdateAttributes ( $index, $attrs, $values )
|
1129
|
-
{
|
1130
|
-
// verify everything
|
1131
|
-
assert ( is_string($index) );
|
1132
|
-
|
1133
|
-
assert ( is_array($attrs) );
|
1134
|
-
foreach ( $attrs as $attr )
|
1135
|
-
assert ( is_string($attr) );
|
1136
|
-
|
1137
|
-
assert ( is_array($values) );
|
1138
|
-
foreach ( $values as $id=>$entry )
|
1139
|
-
{
|
1140
|
-
assert ( is_numeric($id) );
|
1141
|
-
assert ( is_array($entry) );
|
1142
|
-
assert ( count($entry)==count($attrs) );
|
1143
|
-
foreach ( $entry as $v )
|
1144
|
-
assert ( is_int($v) );
|
1145
|
-
}
|
1146
|
-
|
1147
|
-
// build request
|
1148
|
-
$req = pack ( "N", strlen($index) ) . $index;
|
1149
|
-
|
1150
|
-
$req .= pack ( "N", count($attrs) );
|
1151
|
-
foreach ( $attrs as $attr )
|
1152
|
-
$req .= pack ( "N", strlen($attr) ) . $attr;
|
1153
|
-
|
1154
|
-
$req .= pack ( "N", count($values) );
|
1155
|
-
foreach ( $values as $id=>$entry )
|
1156
|
-
{
|
1157
|
-
$req .= sphPack64 ( $id );
|
1158
|
-
foreach ( $entry as $v )
|
1159
|
-
$req .= pack ( "N", $v );
|
1160
|
-
}
|
1161
|
-
|
1162
|
-
// Line for testing Riddle:
|
1163
|
-
return $req;
|
1164
|
-
|
1165
|
-
// mbstring workaround
|
1166
|
-
$this->_MBPush ();
|
1167
|
-
|
1168
|
-
// connect, send query, get response
|
1169
|
-
if (!( $fp = $this->_Connect() ))
|
1170
|
-
{
|
1171
|
-
$this->_MBPop ();
|
1172
|
-
return -1;
|
1173
|
-
}
|
1174
|
-
|
1175
|
-
$len = strlen($req);
|
1176
|
-
$req = pack ( "nnN", SEARCHD_COMMAND_UPDATE, VER_COMMAND_UPDATE, $len ) . $req; // add header
|
1177
|
-
fwrite ( $fp, $req, $len+8 );
|
1178
|
-
|
1179
|
-
if (!( $response = $this->_GetResponse ( $fp, VER_COMMAND_UPDATE ) ))
|
1180
|
-
{
|
1181
|
-
$this->_MBPop ();
|
1182
|
-
return -1;
|
1183
|
-
}
|
1184
|
-
|
1185
|
-
// parse response
|
1186
|
-
list(,$updated) = unpack ( "N*", substr ( $response, 0, 4 ) );
|
1187
|
-
$this->_MBPop ();
|
1188
|
-
return $updated;
|
1189
|
-
}
|
1190
|
-
|
1191
|
-
// Function for Riddle, copied from internal code.
|
1192
|
-
function FilterOutput()
|
1193
|
-
{
|
1194
|
-
foreach ( $this->_filters as $filter )
|
1195
|
-
{
|
1196
|
-
$req .= pack ( "N", strlen($filter["attr"]) ) . $filter["attr"];
|
1197
|
-
$req .= pack ( "N", $filter["type"] );
|
1198
|
-
switch ( $filter["type"] )
|
1199
|
-
{
|
1200
|
-
case SPH_FILTER_VALUES:
|
1201
|
-
$req .= pack ( "N", count($filter["values"]) );
|
1202
|
-
foreach ( $filter["values"] as $value )
|
1203
|
-
$req .= pack ( "N", floatval($value) ); // this uberhack is to workaround 32bit signed int limit on x32 platforms
|
1204
|
-
break;
|
1205
|
-
|
1206
|
-
case SPH_FILTER_RANGE:
|
1207
|
-
$req .= pack ( "NN", $filter["min"], $filter["max"] );
|
1208
|
-
break;
|
1209
|
-
|
1210
|
-
case SPH_FILTER_FLOATRANGE:
|
1211
|
-
$req .= $this->_PackFloat ( $filter["min"] ) . $this->_PackFloat ( $filter["max"] );
|
1212
|
-
break;
|
1213
|
-
|
1214
|
-
default:
|
1215
|
-
assert ( 0 && "internal error: unhandled filter type" );
|
1216
|
-
}
|
1217
|
-
$req .= pack ( "N", $filter["exclude"] );
|
1218
|
-
}
|
1219
|
-
|
1220
|
-
return $req;
|
1221
|
-
}
|
1222
|
-
}
|
1223
|
-
|
1224
|
-
//
|
1225
|
-
// $Id: sphinxapi.php 1418 2008-08-28 15:30:05Z shodan $
|
1226
|
-
//
|
1227
|
-
|
1228
|
-
?>
|