riddle 1.0.3 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/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
|
-
?>
|