riddle 0.9.8.1112

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