riddle 0.9.8.1533.10 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. data/README.textile +1 -0
  2. data/lib/riddle.rb +9 -15
  3. data/lib/riddle/0.9.8.rb +0 -0
  4. data/lib/riddle/0.9.9.rb +5 -0
  5. data/lib/riddle/0.9.9/client.rb +49 -0
  6. data/lib/riddle/0.9.9/client/filter.rb +22 -0
  7. data/lib/riddle/0.9.9/configuration/searchd.rb +28 -0
  8. data/lib/riddle/client.rb +110 -18
  9. data/lib/riddle/client/filter.rb +29 -20
  10. data/lib/riddle/client/message.rb +4 -0
  11. data/lib/riddle/client/response.rb +10 -0
  12. data/lib/riddle/configuration/distributed_index.rb +8 -7
  13. data/lib/riddle/configuration/index.rb +15 -11
  14. data/lib/riddle/configuration/searchd.rb +6 -4
  15. data/lib/riddle/configuration/sql_source.rb +9 -4
  16. data/lib/riddle/controller.rb +5 -3
  17. data/spec/fixtures/data_generator.0.9.8.php +208 -0
  18. data/spec/fixtures/data_generator.0.9.9.php +225 -0
  19. data/spec/fixtures/sphinx/configuration.erb +38 -0
  20. data/spec/fixtures/sphinx/people.spa +0 -0
  21. data/spec/fixtures/sphinx/people.spd +0 -0
  22. data/spec/fixtures/sphinx/people.sph +0 -0
  23. data/spec/fixtures/sphinx/people.spi +0 -0
  24. data/spec/fixtures/sphinx/people.spk +0 -0
  25. data/spec/fixtures/sphinx/people.spm +0 -0
  26. data/spec/fixtures/sphinx/people.spp +0 -0
  27. data/spec/fixtures/sphinx/searchd.log +3731 -0
  28. data/spec/fixtures/sphinx/searchd.query.log +1032 -0
  29. data/spec/fixtures/sphinx/spec.conf +38 -0
  30. data/spec/fixtures/sphinxapi.0.9.8.php +1228 -0
  31. data/spec/fixtures/sphinxapi.0.9.9.php +1646 -0
  32. data/spec/fixtures/sql/conf.example.yml +3 -0
  33. data/spec/fixtures/sql/conf.yml +3 -0
  34. data/spec/fixtures/sql/data.sql +25000 -0
  35. data/spec/fixtures/sql/structure.sql +16 -0
  36. data/spec/functional/excerpt_spec.rb +37 -10
  37. data/spec/functional/persistance_spec.rb +17 -0
  38. data/spec/functional/status_spec.rb +21 -0
  39. data/spec/functional/update_spec.rb +3 -3
  40. data/spec/spec_helper.rb +30 -0
  41. data/spec/sphinx_helper.rb +93 -0
  42. data/spec/unit/client_spec.rb +20 -3
  43. data/spec/unit/configuration/distributed_index_spec.rb +2 -0
  44. data/spec/unit/configuration/index_spec.rb +16 -0
  45. data/spec/unit/configuration/searchd_spec.rb +46 -13
  46. data/spec/unit/configuration/sql_source_spec.rb +15 -0
  47. metadata +61 -37
  48. data/MIT-LICENCE +0 -20
  49. data/lib/tabtab_definitions.rb +0 -15
@@ -0,0 +1,1646 @@
1
+ <?php
2
+
3
+ //
4
+ // $Id: sphinxapi.php 1775 2009-04-06 22:15:58Z 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
+ define ( "SEARCHD_COMMAND_PERSIST", 4 );
26
+ define ( "SEARCHD_COMMAND_STATUS", 5 );
27
+ define ( "SEARCHD_COMMAND_QUERY", 6 );
28
+
29
+ /// current client-side command implementation versions
30
+ define ( "VER_COMMAND_SEARCH", 0x116 );
31
+ define ( "VER_COMMAND_EXCERPT", 0x100 );
32
+ define ( "VER_COMMAND_UPDATE", 0x102 );
33
+ define ( "VER_COMMAND_KEYWORDS", 0x100 );
34
+ define ( "VER_COMMAND_STATUS", 0x100 );
35
+ define ( "VER_COMMAND_QUERY", 0x100 );
36
+
37
+ /// known searchd status codes
38
+ define ( "SEARCHD_OK", 0 );
39
+ define ( "SEARCHD_ERROR", 1 );
40
+ define ( "SEARCHD_RETRY", 2 );
41
+ define ( "SEARCHD_WARNING", 3 );
42
+
43
+ /// known match modes
44
+ define ( "SPH_MATCH_ALL", 0 );
45
+ define ( "SPH_MATCH_ANY", 1 );
46
+ define ( "SPH_MATCH_PHRASE", 2 );
47
+ define ( "SPH_MATCH_BOOLEAN", 3 );
48
+ define ( "SPH_MATCH_EXTENDED", 4 );
49
+ define ( "SPH_MATCH_FULLSCAN", 5 );
50
+ define ( "SPH_MATCH_EXTENDED2", 6 ); // extended engine V2 (TEMPORARY, WILL BE REMOVED)
51
+
52
+ /// known ranking modes (ext2 only)
53
+ define ( "SPH_RANK_PROXIMITY_BM25", 0 ); ///< default mode, phrase proximity major factor and BM25 minor one
54
+ define ( "SPH_RANK_BM25", 1 ); ///< statistical mode, BM25 ranking only (faster but worse quality)
55
+ define ( "SPH_RANK_NONE", 2 ); ///< no ranking, all matches get a weight of 1
56
+ define ( "SPH_RANK_WORDCOUNT", 3 ); ///< simple word-count weighting, rank is a weighted sum of per-field keyword occurence counts
57
+ define ( "SPH_RANK_PROXIMITY", 4 );
58
+ define ( "SPH_RANK_MATCHANY", 5 );
59
+ define ( "SPH_RANK_FIELDMASK", 6 );
60
+
61
+ /// known sort modes
62
+ define ( "SPH_SORT_RELEVANCE", 0 );
63
+ define ( "SPH_SORT_ATTR_DESC", 1 );
64
+ define ( "SPH_SORT_ATTR_ASC", 2 );
65
+ define ( "SPH_SORT_TIME_SEGMENTS", 3 );
66
+ define ( "SPH_SORT_EXTENDED", 4 );
67
+ define ( "SPH_SORT_EXPR", 5 );
68
+
69
+ /// known filter types
70
+ define ( "SPH_FILTER_VALUES", 0 );
71
+ define ( "SPH_FILTER_RANGE", 1 );
72
+ define ( "SPH_FILTER_FLOATRANGE", 2 );
73
+
74
+ /// known attribute types
75
+ define ( "SPH_ATTR_INTEGER", 1 );
76
+ define ( "SPH_ATTR_TIMESTAMP", 2 );
77
+ define ( "SPH_ATTR_ORDINAL", 3 );
78
+ define ( "SPH_ATTR_BOOL", 4 );
79
+ define ( "SPH_ATTR_FLOAT", 5 );
80
+ define ( "SPH_ATTR_BIGINT", 6 );
81
+ define ( "SPH_ATTR_MULTI", 0x40000000 );
82
+
83
+ /// known grouping functions
84
+ define ( "SPH_GROUPBY_DAY", 0 );
85
+ define ( "SPH_GROUPBY_WEEK", 1 );
86
+ define ( "SPH_GROUPBY_MONTH", 2 );
87
+ define ( "SPH_GROUPBY_YEAR", 3 );
88
+ define ( "SPH_GROUPBY_ATTR", 4 );
89
+ define ( "SPH_GROUPBY_ATTRPAIR", 5 );
90
+
91
+ // important properties of PHP's integers:
92
+ // - always signed (one bit short of PHP_INT_SIZE)
93
+ // - conversion from string to int is saturated
94
+ // - float is double
95
+ // - div converts arguments to floats
96
+ // - mod converts arguments to ints
97
+
98
+ // the packing code below works as follows:
99
+ // - when we got an int, just pack it
100
+ // if performance is a problem, this is the branch users should aim for
101
+ //
102
+ // - otherwise, we got a number in string form
103
+ // this might be due to different reasons, but we assume that this is
104
+ // because it didn't fit into PHP int
105
+ //
106
+ // - factor the string into high and low ints for packing
107
+ // - if we have bcmath, then it is used
108
+ // - if we don't, we have to do it manually (this is the fun part)
109
+ //
110
+ // - x64 branch does factoring using ints
111
+ // - x32 (ab)uses floats, since we can't fit unsigned 32-bit number into an int
112
+ //
113
+ // unpacking routines are pretty much the same.
114
+ // - return ints if we can
115
+ // - otherwise format number into a string
116
+
117
+ /// pack 64-bit signed
118
+ function sphPackI64 ( $v )
119
+ {
120
+ assert ( is_numeric($v) );
121
+
122
+ // x64
123
+ if ( PHP_INT_SIZE>=8 )
124
+ {
125
+ $v = (int)$v;
126
+ return pack ( "NN", $v>>32, $v&0xFFFFFFFF );
127
+ }
128
+
129
+ // x32, int
130
+ if ( is_int($v) )
131
+ return pack ( "NN", $v < 0 ? -1 : 0, $v );
132
+
133
+ // x32, bcmath
134
+ if ( function_exists("bcmul") )
135
+ {
136
+ if ( bccomp ( $v, 0 ) == -1 )
137
+ $v = bcadd ( "18446744073709551616", $v );
138
+ $h = bcdiv ( $v, "4294967296", 0 );
139
+ $l = bcmod ( $v, "4294967296" );
140
+ return pack ( "NN", (float)$h, (float)$l ); // conversion to float is intentional; int would lose 31st bit
141
+ }
142
+
143
+ // x32, no-bcmath
144
+ $p = max(0, strlen($v) - 13);
145
+ $lo = abs((float)substr($v, $p));
146
+ $hi = abs((float)substr($v, 0, $p));
147
+
148
+ $m = $lo + $hi*1316134912.0; // (10 ^ 13) % (1 << 32) = 1316134912
149
+ $q = floor($m/4294967296.0);
150
+ $l = $m - ($q*4294967296.0);
151
+ $h = $hi*2328.0 + $q; // (10 ^ 13) / (1 << 32) = 2328
152
+
153
+ if ( $v<0 )
154
+ {
155
+ if ( $l==0 )
156
+ $h = 4294967296.0 - $h;
157
+ else
158
+ {
159
+ $h = 4294967295.0 - $h;
160
+ $l = 4294967296.0 - $l;
161
+ }
162
+ }
163
+ return pack ( "NN", $h, $l );
164
+ }
165
+
166
+ /// pack 64-bit unsigned
167
+ function sphPackU64 ( $v )
168
+ {
169
+ assert ( is_numeric($v) );
170
+
171
+ // x64
172
+ if ( PHP_INT_SIZE>=8 )
173
+ {
174
+ assert ( $v>=0 );
175
+
176
+ // x64, int
177
+ if ( is_int($v) )
178
+ return pack ( "NN", $v>>32, $v&0xFFFFFFFF );
179
+
180
+ // x64, bcmath
181
+ if ( function_exists("bcmul") )
182
+ {
183
+ $h = bcdiv ( $v, 4294967296, 0 );
184
+ $l = bcmod ( $v, 4294967296 );
185
+ return pack ( "NN", $h, $l );
186
+ }
187
+
188
+ // x64, no-bcmath
189
+ $p = max ( 0, strlen($v) - 13 );
190
+ $lo = (int)substr ( $v, $p );
191
+ $hi = (int)substr ( $v, 0, $p );
192
+
193
+ $m = $lo + $hi*1316134912;
194
+ $l = $m % 4294967296;
195
+ $h = $hi*2328 + (int)($m/4294967296);
196
+
197
+ return pack ( "NN", $h, $l );
198
+ }
199
+
200
+ // x32, int
201
+ if ( is_int($v) )
202
+ return pack ( "NN", 0, $v );
203
+
204
+ // x32, bcmath
205
+ if ( function_exists("bcmul") )
206
+ {
207
+ $h = bcdiv ( $v, "4294967296", 0 );
208
+ $l = bcmod ( $v, "4294967296" );
209
+ return pack ( "NN", (float)$h, (float)$l ); // conversion to float is intentional; int would lose 31st bit
210
+ }
211
+
212
+ // x32, no-bcmath
213
+ $p = max(0, strlen($v) - 13);
214
+ $lo = (float)substr($v, $p);
215
+ $hi = (float)substr($v, 0, $p);
216
+
217
+ $m = $lo + $hi*1316134912.0;
218
+ $q = floor($m / 4294967296.0);
219
+ $l = $m - ($q * 4294967296.0);
220
+ $h = $hi*2328.0 + $q;
221
+
222
+ return pack ( "NN", $h, $l );
223
+ }
224
+
225
+ // unpack 64-bit unsigned
226
+ function sphUnpackU64 ( $v )
227
+ {
228
+ list ( $hi, $lo ) = array_values ( unpack ( "N*N*", $v ) );
229
+
230
+ if ( PHP_INT_SIZE>=8 )
231
+ {
232
+ if ( $hi<0 ) $hi += (1<<32); // because php 5.2.2 to 5.2.5 is totally fucked up again
233
+ if ( $lo<0 ) $lo += (1<<32);
234
+
235
+ // x64, int
236
+ if ( $hi<=2147483647 )
237
+ return ($hi<<32) + $lo;
238
+
239
+ // x64, bcmath
240
+ if ( function_exists("bcmul") )
241
+ return bcadd ( $lo, bcmul ( $hi, "4294967296" ) );
242
+
243
+ // x64, no-bcmath
244
+ $C = 100000;
245
+ $h = ((int)($hi / $C) << 32) + (int)($lo / $C);
246
+ $l = (($hi % $C) << 32) + ($lo % $C);
247
+ if ( $l>$C )
248
+ {
249
+ $h += (int)($l / $C);
250
+ $l = $l % $C;
251
+ }
252
+
253
+ if ( $h==0 )
254
+ return $l;
255
+ return sprintf ( "%d%05d", $h, $l );
256
+ }
257
+
258
+ // x32, int
259
+ if ( $hi==0 )
260
+ {
261
+ if ( $lo>0 )
262
+ return $lo;
263
+ return sprintf ( "%u", $lo );
264
+ }
265
+
266
+ $hi = sprintf ( "%u", $hi );
267
+ $lo = sprintf ( "%u", $lo );
268
+
269
+ // x32, bcmath
270
+ if ( function_exists("bcmul") )
271
+ return bcadd ( $lo, bcmul ( $hi, "4294967296" ) );
272
+
273
+ // x32, no-bcmath
274
+ $hi = (float)$hi;
275
+ $lo = (float)$lo;
276
+
277
+ $q = floor($hi/10000000.0);
278
+ $r = $hi - $q*10000000.0;
279
+ $m = $lo + $r*4967296.0;
280
+ $mq = floor($m/10000000.0);
281
+ $l = $m - $mq*10000000.0;
282
+ $h = $q*4294967296.0 + $r*429.0 + $mq;
283
+
284
+ $h = sprintf ( "%.0f", $h );
285
+ $l = sprintf ( "%07.0f", $l );
286
+ if ( $h=="0" )
287
+ return sprintf( "%.0f", (float)$l );
288
+ return $h . $l;
289
+ }
290
+
291
+ // unpack 64-bit signed
292
+ function sphUnpackI64 ( $v )
293
+ {
294
+ list ( $hi, $lo ) = array_values ( unpack ( "N*N*", $v ) );
295
+
296
+ // x64
297
+ if ( PHP_INT_SIZE>=8 )
298
+ {
299
+ if ( $hi<0 ) $hi += (1<<32); // because php 5.2.2 to 5.2.5 is totally fucked up again
300
+ if ( $lo<0 ) $lo += (1<<32);
301
+
302
+ return ($hi<<32) + $lo;
303
+ }
304
+
305
+ // x32, int
306
+ if ( $hi==0 )
307
+ {
308
+ if ( $lo>0 )
309
+ return $lo;
310
+ return sprintf ( "%u", $lo );
311
+ }
312
+ // x32, int
313
+ elseif ( $hi==-1 )
314
+ {
315
+ if ( $lo<0 )
316
+ return $lo;
317
+ return sprintf ( "%.0f", $lo - 4294967296.0 );
318
+ }
319
+
320
+ $neg = "";
321
+ $c = 0;
322
+ if ( $hi<0 )
323
+ {
324
+ $hi = ~$hi;
325
+ $lo = ~$lo;
326
+ $c = 1;
327
+ $neg = "-";
328
+ }
329
+
330
+ $hi = sprintf ( "%u", $hi );
331
+ $lo = sprintf ( "%u", $lo );
332
+
333
+ // x32, bcmath
334
+ if ( function_exists("bcmul") )
335
+ return $neg . bcadd ( bcadd ( $lo, bcmul ( $hi, "4294967296" ) ), $c );
336
+
337
+ // x32, no-bcmath
338
+ $hi = (float)$hi;
339
+ $lo = (float)$lo;
340
+
341
+ $q = floor($hi/10000000.0);
342
+ $r = $hi - $q*10000000.0;
343
+ $m = $lo + $r*4967296.0;
344
+ $mq = floor($m/10000000.0);
345
+ $l = $m - $mq*10000000.0 + $c;
346
+ $h = $q*4294967296.0 + $r*429.0 + $mq;
347
+
348
+ $h = sprintf ( "%.0f", $h );
349
+ $l = sprintf ( "%07.0f", $l );
350
+ if ( $h=="0" )
351
+ return $neg . sprintf( "%.0f", (float)$l );
352
+ return $neg . $h . $l;
353
+ }
354
+
355
+
356
+ /// sphinx searchd client class
357
+ class SphinxClient
358
+ {
359
+ var $_host; ///< searchd host (default is "localhost")
360
+ var $_port; ///< searchd port (default is 3312)
361
+ var $_offset; ///< how many records to seek from result-set start (default is 0)
362
+ var $_limit; ///< how many records to return from result-set starting at offset (default is 20)
363
+ var $_mode; ///< query matching mode (default is SPH_MATCH_ALL)
364
+ var $_weights; ///< per-field weights (default is 1 for all fields)
365
+ var $_sort; ///< match sorting mode (default is SPH_SORT_RELEVANCE)
366
+ var $_sortby; ///< attribute to sort by (defualt is "")
367
+ var $_min_id; ///< min ID to match (default is 0, which means no limit)
368
+ var $_max_id; ///< max ID to match (default is 0, which means no limit)
369
+ var $_filters; ///< search filters
370
+ var $_groupby; ///< group-by attribute name
371
+ var $_groupfunc; ///< group-by function (to pre-process group-by attribute value with)
372
+ var $_groupsort; ///< group-by sorting clause (to sort groups in result set with)
373
+ var $_groupdistinct;///< group-by count-distinct attribute
374
+ var $_maxmatches; ///< max matches to retrieve
375
+ var $_cutoff; ///< cutoff to stop searching at (default is 0)
376
+ var $_retrycount; ///< distributed retries count
377
+ var $_retrydelay; ///< distributed retries delay
378
+ var $_anchor; ///< geographical anchor point
379
+ var $_indexweights; ///< per-index weights
380
+ var $_ranker; ///< ranking mode (default is SPH_RANK_PROXIMITY_BM25)
381
+ var $_maxquerytime; ///< max query time, milliseconds (default is 0, do not limit)
382
+ var $_fieldweights; ///< per-field-name weights
383
+ var $_overrides; ///< per-query attribute values overrides
384
+ var $_select; ///< select-list (attributes or expressions, with optional aliases)
385
+
386
+ var $_error; ///< last error message
387
+ var $_warning; ///< last warning message
388
+ var $_connerror; ///< connection error vs remote error flag
389
+
390
+ var $_reqs; ///< requests array for multi-query
391
+ var $_mbenc; ///< stored mbstring encoding
392
+ var $_arrayresult; ///< whether $result["matches"] should be a hash or an array
393
+ var $_timeout; ///< connect timeout
394
+
395
+ /////////////////////////////////////////////////////////////////////////////
396
+ // common stuff
397
+ /////////////////////////////////////////////////////////////////////////////
398
+
399
+ /// create a new client object and fill defaults
400
+ function SphinxClient ()
401
+ {
402
+ // per-client-object settings
403
+ $this->_host = "localhost";
404
+ $this->_port = 3312;
405
+ $this->_path = false;
406
+ $this->_socket = false;
407
+
408
+ // per-query settings
409
+ $this->_offset = 0;
410
+ $this->_limit = 20;
411
+ $this->_mode = SPH_MATCH_ALL;
412
+ $this->_weights = array ();
413
+ $this->_sort = SPH_SORT_RELEVANCE;
414
+ $this->_sortby = "";
415
+ $this->_min_id = 0;
416
+ $this->_max_id = 0;
417
+ $this->_filters = array ();
418
+ $this->_groupby = "";
419
+ $this->_groupfunc = SPH_GROUPBY_DAY;
420
+ $this->_groupsort = "@group desc";
421
+ $this->_groupdistinct= "";
422
+ $this->_maxmatches = 1000;
423
+ $this->_cutoff = 0;
424
+ $this->_retrycount = 0;
425
+ $this->_retrydelay = 0;
426
+ $this->_anchor = array ();
427
+ $this->_indexweights= array ();
428
+ $this->_ranker = SPH_RANK_PROXIMITY_BM25;
429
+ $this->_maxquerytime= 0;
430
+ $this->_fieldweights= array();
431
+ $this->_overrides = array();
432
+ $this->_select = "*";
433
+
434
+ $this->_error = ""; // per-reply fields (for single-query case)
435
+ $this->_warning = "";
436
+ $this->_connerror = false;
437
+
438
+ $this->_reqs = array (); // requests storage (for multi-query case)
439
+ $this->_mbenc = "";
440
+ $this->_arrayresult = false;
441
+ $this->_timeout = 0;
442
+ }
443
+
444
+ function __destruct()
445
+ {
446
+ if ( $this->_socket !== false )
447
+ fclose ( $this->_socket );
448
+ }
449
+
450
+ /// get last error message (string)
451
+ function GetLastError ()
452
+ {
453
+ return $this->_error;
454
+ }
455
+
456
+ /// get last warning message (string)
457
+ function GetLastWarning ()
458
+ {
459
+ return $this->_warning;
460
+ }
461
+
462
+ /// get last error flag (to tell network connection errors from searchd errors or broken responses)
463
+ function IsConnectError()
464
+ {
465
+ return $this->_connerror;
466
+ }
467
+
468
+ /// set searchd host name (string) and port (integer)
469
+ function SetServer ( $host, $port = 0 )
470
+ {
471
+ assert ( is_string($host) );
472
+ if ( $host[0] == '/')
473
+ {
474
+ $this->_path = 'unix://' . $host;
475
+ return;
476
+ }
477
+ if ( substr ( $host, 0, 7 )=="unix://" )
478
+ {
479
+ $this->_path = $host;
480
+ return;
481
+ }
482
+
483
+ assert ( is_int($port) );
484
+ $this->_host = $host;
485
+ $this->_port = $port;
486
+ $this->_path = '';
487
+
488
+ }
489
+
490
+ /// set server connection timeout (0 to remove)
491
+ function SetConnectTimeout ( $timeout )
492
+ {
493
+ assert ( is_numeric($timeout) );
494
+ $this->_timeout = $timeout;
495
+ }
496
+
497
+
498
+ function _Send ( $handle, $data, $length )
499
+ {
500
+ if ( feof($handle) || fwrite ( $handle, $data, $length ) !== $length )
501
+ {
502
+ $this->_error = 'connection unexpectedly closed (timed out?)';
503
+ $this->_connerror = true;
504
+ return false;
505
+ }
506
+ return true;
507
+ }
508
+
509
+ /////////////////////////////////////////////////////////////////////////////
510
+
511
+ /// enter mbstring workaround mode
512
+ function _MBPush ()
513
+ {
514
+ $this->_mbenc = "";
515
+ if ( ini_get ( "mbstring.func_overload" ) & 2 )
516
+ {
517
+ $this->_mbenc = mb_internal_encoding();
518
+ mb_internal_encoding ( "latin1" );
519
+ }
520
+ }
521
+
522
+ /// leave mbstring workaround mode
523
+ function _MBPop ()
524
+ {
525
+ if ( $this->_mbenc )
526
+ mb_internal_encoding ( $this->_mbenc );
527
+ }
528
+
529
+ /// connect to searchd server
530
+ function _Connect ()
531
+ {
532
+ if ( $this->_socket !== false )
533
+ return $this->_socket;
534
+
535
+ $errno = 0;
536
+ $errstr = "";
537
+ $this->_connerror = false;
538
+
539
+ if ( $this->_path )
540
+ {
541
+ $host = $this->_path;
542
+ $port = 0;
543
+ }
544
+ else
545
+ {
546
+ $host = $this->_host;
547
+ $port = $this->_port;
548
+ }
549
+
550
+ if ( $this->_timeout<=0 )
551
+ $fp = @fsockopen ( $host, $port, $errno, $errstr );
552
+ else
553
+ $fp = @fsockopen ( $host, $port, $errno, $errstr, $this->_timeout );
554
+
555
+ if ( !$fp )
556
+ {
557
+ if ( $this->_path )
558
+ $location = $this->_path;
559
+ else
560
+ $location = "{$this->_host}:{$this->_port}";
561
+
562
+ $errstr = trim ( $errstr );
563
+ $this->_error = "connection to $location failed (errno=$errno, msg=$errstr)";
564
+ $this->_connerror = true;
565
+ return false;
566
+ }
567
+
568
+ // send my version
569
+ // this is a subtle part. we must do it before (!) reading back from searchd.
570
+ // because otherwise under some conditions (reported on FreeBSD for instance)
571
+ // TCP stack could throttle write-write-read pattern because of Nagle.
572
+ if ( !$this->_Send ( $fp, pack ( "N", 1 ), 4 ) )
573
+ {
574
+ fclose ( $fp );
575
+ $this->_error = "failed to send client protocol version";
576
+ return false;
577
+ }
578
+
579
+ // check version
580
+ list(,$v) = unpack ( "N*", fread ( $fp, 4 ) );
581
+ $v = (int)$v;
582
+ if ( $v<1 )
583
+ {
584
+ fclose ( $fp );
585
+ $this->_error = "expected searchd protocol version 1+, got version '$v'";
586
+ return false;
587
+ }
588
+
589
+ return $fp;
590
+ }
591
+
592
+ /// get and check response packet from searchd server
593
+ function _GetResponse ( $fp, $client_ver )
594
+ {
595
+ $response = "";
596
+ $len = 0;
597
+
598
+ $header = fread ( $fp, 8 );
599
+ if ( strlen($header)==8 )
600
+ {
601
+ list ( $status, $ver, $len ) = array_values ( unpack ( "n2a/Nb", $header ) );
602
+ $left = $len;
603
+ while ( $left>0 && !feof($fp) )
604
+ {
605
+ $chunk = fread ( $fp, $left );
606
+ if ( $chunk )
607
+ {
608
+ $response .= $chunk;
609
+ $left -= strlen($chunk);
610
+ }
611
+ }
612
+ }
613
+ if ( $this->_socket === false )
614
+ fclose ( $fp );
615
+
616
+ // check response
617
+ $read = strlen ( $response );
618
+ if ( !$response || $read!=$len )
619
+ {
620
+ $this->_error = $len
621
+ ? "failed to read searchd response (status=$status, ver=$ver, len=$len, read=$read)"
622
+ : "received zero-sized searchd response";
623
+ return false;
624
+ }
625
+
626
+ // check status
627
+ if ( $status==SEARCHD_WARNING )
628
+ {
629
+ list(,$wlen) = unpack ( "N*", substr ( $response, 0, 4 ) );
630
+ $this->_warning = substr ( $response, 4, $wlen );
631
+ return substr ( $response, 4+$wlen );
632
+ }
633
+ if ( $status==SEARCHD_ERROR )
634
+ {
635
+ $this->_error = "searchd error: " . substr ( $response, 4 );
636
+ return false;
637
+ }
638
+ if ( $status==SEARCHD_RETRY )
639
+ {
640
+ $this->_error = "temporary searchd error: " . substr ( $response, 4 );
641
+ return false;
642
+ }
643
+ if ( $status!=SEARCHD_OK )
644
+ {
645
+ $this->_error = "unknown status code '$status'";
646
+ return false;
647
+ }
648
+
649
+ // check version
650
+ if ( $ver<$client_ver )
651
+ {
652
+ $this->_warning = sprintf ( "searchd command v.%d.%d older than client's v.%d.%d, some options might not work",
653
+ $ver>>8, $ver&0xff, $client_ver>>8, $client_ver&0xff );
654
+ }
655
+
656
+ return $response;
657
+ }
658
+
659
+ /////////////////////////////////////////////////////////////////////////////
660
+ // searching
661
+ /////////////////////////////////////////////////////////////////////////////
662
+
663
+ /// set offset and count into result set,
664
+ /// and optionally set max-matches and cutoff limits
665
+ function SetLimits ( $offset, $limit, $max=0, $cutoff=0 )
666
+ {
667
+ assert ( is_int($offset) );
668
+ assert ( is_int($limit) );
669
+ assert ( $offset>=0 );
670
+ assert ( $limit>0 );
671
+ assert ( $max>=0 );
672
+ $this->_offset = $offset;
673
+ $this->_limit = $limit;
674
+ if ( $max>0 )
675
+ $this->_maxmatches = $max;
676
+ if ( $cutoff>0 )
677
+ $this->_cutoff = $cutoff;
678
+ }
679
+
680
+ /// set maximum query time, in milliseconds, per-index
681
+ /// integer, 0 means "do not limit"
682
+ function SetMaxQueryTime ( $max )
683
+ {
684
+ assert ( is_int($max) );
685
+ assert ( $max>=0 );
686
+ $this->_maxquerytime = $max;
687
+ }
688
+
689
+ /// set matching mode
690
+ function SetMatchMode ( $mode )
691
+ {
692
+ assert ( $mode==SPH_MATCH_ALL
693
+ || $mode==SPH_MATCH_ANY
694
+ || $mode==SPH_MATCH_PHRASE
695
+ || $mode==SPH_MATCH_BOOLEAN
696
+ || $mode==SPH_MATCH_EXTENDED
697
+ || $mode==SPH_MATCH_FULLSCAN
698
+ || $mode==SPH_MATCH_EXTENDED2 );
699
+ $this->_mode = $mode;
700
+ }
701
+
702
+ /// set ranking mode
703
+ function SetRankingMode ( $ranker )
704
+ {
705
+ assert ( $ranker==SPH_RANK_PROXIMITY_BM25
706
+ || $ranker==SPH_RANK_BM25
707
+ || $ranker==SPH_RANK_NONE
708
+ || $ranker==SPH_RANK_WORDCOUNT
709
+ || $ranker==SPH_RANK_PROXIMITY );
710
+ $this->_ranker = $ranker;
711
+ }
712
+
713
+ /// set matches sorting mode
714
+ function SetSortMode ( $mode, $sortby="" )
715
+ {
716
+ assert (
717
+ $mode==SPH_SORT_RELEVANCE ||
718
+ $mode==SPH_SORT_ATTR_DESC ||
719
+ $mode==SPH_SORT_ATTR_ASC ||
720
+ $mode==SPH_SORT_TIME_SEGMENTS ||
721
+ $mode==SPH_SORT_EXTENDED ||
722
+ $mode==SPH_SORT_EXPR );
723
+ assert ( is_string($sortby) );
724
+ assert ( $mode==SPH_SORT_RELEVANCE || strlen($sortby)>0 );
725
+
726
+ $this->_sort = $mode;
727
+ $this->_sortby = $sortby;
728
+ }
729
+
730
+ /// bind per-field weights by order
731
+ /// DEPRECATED; use SetFieldWeights() instead
732
+ function SetWeights ( $weights )
733
+ {
734
+ assert ( is_array($weights) );
735
+ foreach ( $weights as $weight )
736
+ assert ( is_int($weight) );
737
+
738
+ $this->_weights = $weights;
739
+ }
740
+
741
+ /// bind per-field weights by name
742
+ function SetFieldWeights ( $weights )
743
+ {
744
+ assert ( is_array($weights) );
745
+ foreach ( $weights as $name=>$weight )
746
+ {
747
+ assert ( is_string($name) );
748
+ assert ( is_int($weight) );
749
+ }
750
+ $this->_fieldweights = $weights;
751
+ }
752
+
753
+ /// bind per-index weights by name
754
+ function SetIndexWeights ( $weights )
755
+ {
756
+ assert ( is_array($weights) );
757
+ foreach ( $weights as $index=>$weight )
758
+ {
759
+ assert ( is_string($index) );
760
+ assert ( is_int($weight) );
761
+ }
762
+ $this->_indexweights = $weights;
763
+ }
764
+
765
+ /// set IDs range to match
766
+ /// only match records if document ID is beetwen $min and $max (inclusive)
767
+ function SetIDRange ( $min, $max )
768
+ {
769
+ assert ( is_numeric($min) );
770
+ assert ( is_numeric($max) );
771
+ assert ( $min<=$max );
772
+ $this->_min_id = $min;
773
+ $this->_max_id = $max;
774
+ }
775
+
776
+ /// set values set filter
777
+ /// only match records where $attribute value is in given set
778
+ function SetFilter ( $attribute, $values, $exclude=false )
779
+ {
780
+ assert ( is_string($attribute) );
781
+ assert ( is_array($values) );
782
+ assert ( count($values) );
783
+
784
+ if ( is_array($values) && count($values) )
785
+ {
786
+ foreach ( $values as $value )
787
+ assert ( is_numeric($value) );
788
+
789
+ $this->_filters[] = array ( "type"=>SPH_FILTER_VALUES, "attr"=>$attribute, "exclude"=>$exclude, "values"=>$values );
790
+ }
791
+ }
792
+
793
+ /// set range filter
794
+ /// only match records if $attribute value is beetwen $min and $max (inclusive)
795
+ function SetFilterRange ( $attribute, $min, $max, $exclude=false )
796
+ {
797
+ assert ( is_string($attribute) );
798
+ assert ( is_numeric($min) );
799
+ assert ( is_numeric($max) );
800
+ assert ( $min<=$max );
801
+
802
+ $this->_filters[] = array ( "type"=>SPH_FILTER_RANGE, "attr"=>$attribute, "exclude"=>$exclude, "min"=>$min, "max"=>$max );
803
+ }
804
+
805
+ /// set float range filter
806
+ /// only match records if $attribute value is beetwen $min and $max (inclusive)
807
+ function SetFilterFloatRange ( $attribute, $min, $max, $exclude=false )
808
+ {
809
+ assert ( is_string($attribute) );
810
+ assert ( is_float($min) );
811
+ assert ( is_float($max) );
812
+ assert ( $min<=$max );
813
+
814
+ $this->_filters[] = array ( "type"=>SPH_FILTER_FLOATRANGE, "attr"=>$attribute, "exclude"=>$exclude, "min"=>$min, "max"=>$max );
815
+ }
816
+
817
+ /// setup anchor point for geosphere distance calculations
818
+ /// required to use @geodist in filters and sorting
819
+ /// latitude and longitude must be in radians
820
+ function SetGeoAnchor ( $attrlat, $attrlong, $lat, $long )
821
+ {
822
+ assert ( is_string($attrlat) );
823
+ assert ( is_string($attrlong) );
824
+ assert ( is_float($lat) );
825
+ assert ( is_float($long) );
826
+
827
+ $this->_anchor = array ( "attrlat"=>$attrlat, "attrlong"=>$attrlong, "lat"=>$lat, "long"=>$long );
828
+ }
829
+
830
+ /// set grouping attribute and function
831
+ function SetGroupBy ( $attribute, $func, $groupsort="@group desc" )
832
+ {
833
+ assert ( is_string($attribute) );
834
+ assert ( is_string($groupsort) );
835
+ assert ( $func==SPH_GROUPBY_DAY
836
+ || $func==SPH_GROUPBY_WEEK
837
+ || $func==SPH_GROUPBY_MONTH
838
+ || $func==SPH_GROUPBY_YEAR
839
+ || $func==SPH_GROUPBY_ATTR
840
+ || $func==SPH_GROUPBY_ATTRPAIR );
841
+
842
+ $this->_groupby = $attribute;
843
+ $this->_groupfunc = $func;
844
+ $this->_groupsort = $groupsort;
845
+ }
846
+
847
+ /// set count-distinct attribute for group-by queries
848
+ function SetGroupDistinct ( $attribute )
849
+ {
850
+ assert ( is_string($attribute) );
851
+ $this->_groupdistinct = $attribute;
852
+ }
853
+
854
+ /// set distributed retries count and delay
855
+ function SetRetries ( $count, $delay=0 )
856
+ {
857
+ assert ( is_int($count) && $count>=0 );
858
+ assert ( is_int($delay) && $delay>=0 );
859
+ $this->_retrycount = $count;
860
+ $this->_retrydelay = $delay;
861
+ }
862
+
863
+ /// set result set format (hash or array; hash by default)
864
+ /// PHP specific; needed for group-by-MVA result sets that may contain duplicate IDs
865
+ function SetArrayResult ( $arrayresult )
866
+ {
867
+ assert ( is_bool($arrayresult) );
868
+ $this->_arrayresult = $arrayresult;
869
+ }
870
+
871
+ /// set attribute values override
872
+ /// there can be only one override per attribute
873
+ /// $values must be a hash that maps document IDs to attribute values
874
+ function SetOverride ( $attrname, $attrtype, $values )
875
+ {
876
+ assert ( is_string ( $attrname ) );
877
+ assert ( in_array ( $attrtype, array ( SPH_ATTR_INTEGER, SPH_ATTR_TIMESTAMP, SPH_ATTR_BOOL, SPH_ATTR_FLOAT, SPH_ATTR_BIGINT ) ) );
878
+ assert ( is_array ( $values ) );
879
+
880
+ $this->_overrides[$attrname] = array ( "attr"=>$attrname, "type"=>$attrtype, "values"=>$values );
881
+ }
882
+
883
+ /// set select-list (attributes or expressions), SQL-like syntax
884
+ function SetSelect ( $select )
885
+ {
886
+ assert ( is_string ( $select ) );
887
+ $this->_select = $select;
888
+ }
889
+
890
+ //////////////////////////////////////////////////////////////////////////////
891
+
892
+ /// clear all filters (for multi-queries)
893
+ function ResetFilters ()
894
+ {
895
+ $this->_filters = array();
896
+ $this->_anchor = array();
897
+ }
898
+
899
+ /// clear groupby settings (for multi-queries)
900
+ function ResetGroupBy ()
901
+ {
902
+ $this->_groupby = "";
903
+ $this->_groupfunc = SPH_GROUPBY_DAY;
904
+ $this->_groupsort = "@group desc";
905
+ $this->_groupdistinct= "";
906
+ }
907
+
908
+ /// clear all attribute value overrides (for multi-queries)
909
+ function ResetOverrides ()
910
+ {
911
+ $this->_overrides = array ();
912
+ }
913
+
914
+ //////////////////////////////////////////////////////////////////////////////
915
+
916
+ /// connect to searchd server, run given search query through given indexes,
917
+ /// and return the search results
918
+ function Query ( $query, $index="*", $comment="" )
919
+ {
920
+ assert ( empty($this->_reqs) );
921
+
922
+ $this->AddQuery ( $query, $index, $comment );
923
+ $results = $this->RunQueries ();
924
+ $this->_reqs = array (); // just in case it failed too early
925
+
926
+ if ( !is_array($results) )
927
+ return false; // probably network error; error message should be already filled
928
+
929
+ $this->_error = $results[0]["error"];
930
+ $this->_warning = $results[0]["warning"];
931
+ if ( $results[0]["status"]==SEARCHD_ERROR )
932
+ return false;
933
+ else
934
+ return $results[0];
935
+ }
936
+
937
+ /// helper to pack floats in network byte order
938
+ function _PackFloat ( $f )
939
+ {
940
+ $t1 = pack ( "f", $f ); // machine order
941
+ list(,$t2) = unpack ( "L*", $t1 ); // int in machine order
942
+ return pack ( "N", $t2 );
943
+ }
944
+
945
+ /// add query to multi-query batch
946
+ /// returns index into results array from RunQueries() call
947
+ function AddQuery ( $query, $index="*", $comment="" )
948
+ {
949
+ // mbstring workaround
950
+ $this->_MBPush ();
951
+
952
+ // build request
953
+ $req = pack ( "NNNNN", $this->_offset, $this->_limit, $this->_mode, $this->_ranker, $this->_sort ); // mode and limits
954
+ $req .= pack ( "N", strlen($this->_sortby) ) . $this->_sortby;
955
+ $req .= pack ( "N", strlen($query) ) . $query; // query itself
956
+ $req .= pack ( "N", count($this->_weights) ); // weights
957
+ foreach ( $this->_weights as $weight )
958
+ $req .= pack ( "N", (int)$weight );
959
+ $req .= pack ( "N", strlen($index) ) . $index; // indexes
960
+ $req .= pack ( "N", 1 ); // id64 range marker
961
+ $req .= sphPackU64 ( $this->_min_id ) . sphPackU64 ( $this->_max_id ); // id64 range
962
+
963
+ // filters
964
+ $req .= pack ( "N", count($this->_filters) );
965
+ foreach ( $this->_filters as $filter )
966
+ {
967
+ $req .= pack ( "N", strlen($filter["attr"]) ) . $filter["attr"];
968
+ $req .= pack ( "N", $filter["type"] );
969
+ switch ( $filter["type"] )
970
+ {
971
+ case SPH_FILTER_VALUES:
972
+ $req .= pack ( "N", count($filter["values"]) );
973
+ foreach ( $filter["values"] as $value )
974
+ $req .= sphPackI64 ( $value );
975
+ break;
976
+
977
+ case SPH_FILTER_RANGE:
978
+ $req .= sphPackI64 ( $filter["min"] ) . sphPackI64 ( $filter["max"] );
979
+ break;
980
+
981
+ case SPH_FILTER_FLOATRANGE:
982
+ $req .= $this->_PackFloat ( $filter["min"] ) . $this->_PackFloat ( $filter["max"] );
983
+ break;
984
+
985
+ default:
986
+ assert ( 0 && "internal error: unhandled filter type" );
987
+ }
988
+ $req .= pack ( "N", $filter["exclude"] );
989
+ }
990
+
991
+ // group-by clause, max-matches count, group-sort clause, cutoff count
992
+ $req .= pack ( "NN", $this->_groupfunc, strlen($this->_groupby) ) . $this->_groupby;
993
+ $req .= pack ( "N", $this->_maxmatches );
994
+ $req .= pack ( "N", strlen($this->_groupsort) ) . $this->_groupsort;
995
+ $req .= pack ( "NNN", $this->_cutoff, $this->_retrycount, $this->_retrydelay );
996
+ $req .= pack ( "N", strlen($this->_groupdistinct) ) . $this->_groupdistinct;
997
+
998
+ // anchor point
999
+ if ( empty($this->_anchor) )
1000
+ {
1001
+ $req .= pack ( "N", 0 );
1002
+ } else
1003
+ {
1004
+ $a =& $this->_anchor;
1005
+ $req .= pack ( "N", 1 );
1006
+ $req .= pack ( "N", strlen($a["attrlat"]) ) . $a["attrlat"];
1007
+ $req .= pack ( "N", strlen($a["attrlong"]) ) . $a["attrlong"];
1008
+ $req .= $this->_PackFloat ( $a["lat"] ) . $this->_PackFloat ( $a["long"] );
1009
+ }
1010
+
1011
+ // per-index weights
1012
+ $req .= pack ( "N", count($this->_indexweights) );
1013
+ foreach ( $this->_indexweights as $idx=>$weight )
1014
+ $req .= pack ( "N", strlen($idx) ) . $idx . pack ( "N", $weight );
1015
+
1016
+ // max query time
1017
+ $req .= pack ( "N", $this->_maxquerytime );
1018
+
1019
+ // per-field weights
1020
+ $req .= pack ( "N", count($this->_fieldweights) );
1021
+ foreach ( $this->_fieldweights as $field=>$weight )
1022
+ $req .= pack ( "N", strlen($field) ) . $field . pack ( "N", $weight );
1023
+
1024
+ // comment
1025
+ $req .= pack ( "N", strlen($comment) ) . $comment;
1026
+
1027
+ // attribute overrides
1028
+ $req .= pack ( "N", count($this->_overrides) );
1029
+ foreach ( $this->_overrides as $key => $entry )
1030
+ {
1031
+ $req .= pack ( "N", strlen($entry["attr"]) ) . $entry["attr"];
1032
+ $req .= pack ( "NN", $entry["type"], count($entry["values"]) );
1033
+ foreach ( $entry["values"] as $id=>$val )
1034
+ {
1035
+ assert ( is_numeric($id) );
1036
+ assert ( is_numeric($val) );
1037
+
1038
+ $req .= sphPackU64 ( $id );
1039
+ switch ( $entry["type"] )
1040
+ {
1041
+ case SPH_ATTR_FLOAT: $req .= $this->_PackFloat ( $val ); break;
1042
+ case SPH_ATTR_BIGINT: $req .= sphPackI64 ( $val ); break;
1043
+ default: $req .= pack ( "N", $val ); break;
1044
+ }
1045
+ }
1046
+ }
1047
+
1048
+ // select-list
1049
+ $req .= pack ( "N", strlen($this->_select) ) . $this->_select;
1050
+
1051
+ // mbstring workaround
1052
+ $this->_MBPop ();
1053
+
1054
+ // store request to requests array
1055
+ $this->_reqs[] = $req;
1056
+ return count($this->_reqs)-1;
1057
+ }
1058
+
1059
+ /// connect to searchd, run queries batch, and return an array of result sets
1060
+ function RunQueries ()
1061
+ {
1062
+ if ( empty($this->_reqs) )
1063
+ {
1064
+ $this->_error = "no queries defined, issue AddQuery() first";
1065
+ return false;
1066
+ }
1067
+
1068
+ // mbstring workaround
1069
+ $this->_MBPush ();
1070
+
1071
+ if (!( $fp = $this->_Connect() ))
1072
+ {
1073
+ $this->_MBPop ();
1074
+ return false;
1075
+ }
1076
+
1077
+ // send query, get response
1078
+ $nreqs = count($this->_reqs);
1079
+ $req = join ( "", $this->_reqs );
1080
+ $len = 4+strlen($req);
1081
+ $req = pack ( "nnNN", SEARCHD_COMMAND_SEARCH, VER_COMMAND_SEARCH, $len, $nreqs ) . $req; // add header
1082
+
1083
+ if ( !( $this->_Send ( $fp, $req, $len+8 ) ) ||
1084
+ !( $response = $this->_GetResponse ( $fp, VER_COMMAND_SEARCH ) ) )
1085
+ {
1086
+ $this->_MBPop ();
1087
+ return false;
1088
+ }
1089
+
1090
+ // query sent ok; we can reset reqs now
1091
+ $this->_reqs = array ();
1092
+
1093
+ // parse and return response
1094
+ return $this->_ParseSearchResponse ( $response, $nreqs );
1095
+ }
1096
+
1097
+ /// parse and return search query (or queries) response
1098
+ function _ParseSearchResponse ( $response, $nreqs )
1099
+ {
1100
+ $p = 0; // current position
1101
+ $max = strlen($response); // max position for checks, to protect against broken responses
1102
+
1103
+ $results = array ();
1104
+ for ( $ires=0; $ires<$nreqs && $p<$max; $ires++ )
1105
+ {
1106
+ $results[] = array();
1107
+ $result =& $results[$ires];
1108
+
1109
+ $result["error"] = "";
1110
+ $result["warning"] = "";
1111
+
1112
+ // extract status
1113
+ list(,$status) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
1114
+ $result["status"] = $status;
1115
+ if ( $status!=SEARCHD_OK )
1116
+ {
1117
+ list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
1118
+ $message = substr ( $response, $p, $len ); $p += $len;
1119
+
1120
+ if ( $status==SEARCHD_WARNING )
1121
+ {
1122
+ $result["warning"] = $message;
1123
+ } else
1124
+ {
1125
+ $result["error"] = $message;
1126
+ continue;
1127
+ }
1128
+ }
1129
+
1130
+ // read schema
1131
+ $fields = array ();
1132
+ $attrs = array ();
1133
+
1134
+ list(,$nfields) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
1135
+ while ( $nfields-->0 && $p<$max )
1136
+ {
1137
+ list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
1138
+ $fields[] = substr ( $response, $p, $len ); $p += $len;
1139
+ }
1140
+ $result["fields"] = $fields;
1141
+
1142
+ list(,$nattrs) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
1143
+ while ( $nattrs-->0 && $p<$max )
1144
+ {
1145
+ list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
1146
+ $attr = substr ( $response, $p, $len ); $p += $len;
1147
+ list(,$type) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
1148
+ $attrs[$attr] = $type;
1149
+ }
1150
+ $result["attrs"] = $attrs;
1151
+
1152
+ // read match count
1153
+ list(,$count) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
1154
+ list(,$id64) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
1155
+
1156
+ // read matches
1157
+ $idx = -1;
1158
+ while ( $count-->0 && $p<$max )
1159
+ {
1160
+ // index into result array
1161
+ $idx++;
1162
+
1163
+ // parse document id and weight
1164
+ if ( $id64 )
1165
+ {
1166
+ $doc = sphUnpackU64 ( substr ( $response, $p, 8 ) ); $p += 8;
1167
+ list(,$weight) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
1168
+ }
1169
+ else
1170
+ {
1171
+ list ( $doc, $weight ) = array_values ( unpack ( "N*N*",
1172
+ substr ( $response, $p, 8 ) ) );
1173
+ $p += 8;
1174
+
1175
+ if ( PHP_INT_SIZE>=8 )
1176
+ {
1177
+ // x64 route, workaround broken unpack() in 5.2.2+
1178
+ if ( $doc<0 ) $doc += (1<<32);
1179
+ } else
1180
+ {
1181
+ // x32 route, workaround php signed/unsigned braindamage
1182
+ $doc = sprintf ( "%u", $doc );
1183
+ }
1184
+ }
1185
+ $weight = sprintf ( "%u", $weight );
1186
+
1187
+ // create match entry
1188
+ if ( $this->_arrayresult )
1189
+ $result["matches"][$idx] = array ( "id"=>$doc, "weight"=>$weight );
1190
+ else
1191
+ $result["matches"][$doc]["weight"] = $weight;
1192
+
1193
+ // parse and create attributes
1194
+ $attrvals = array ();
1195
+ foreach ( $attrs as $attr=>$type )
1196
+ {
1197
+ // handle 64bit ints
1198
+ if ( $type==SPH_ATTR_BIGINT )
1199
+ {
1200
+ $attrvals[$attr] = sphUnpackI64 ( substr ( $response, $p, 8 ) ); $p += 8;
1201
+ continue;
1202
+ }
1203
+
1204
+ // handle floats
1205
+ if ( $type==SPH_ATTR_FLOAT )
1206
+ {
1207
+ list(,$uval) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
1208
+ list(,$fval) = unpack ( "f*", pack ( "L", $uval ) );
1209
+ $attrvals[$attr] = $fval;
1210
+ continue;
1211
+ }
1212
+
1213
+ // handle everything else as unsigned ints
1214
+ list(,$val) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
1215
+ if ( $type & SPH_ATTR_MULTI )
1216
+ {
1217
+ $attrvals[$attr] = array ();
1218
+ $nvalues = $val;
1219
+ while ( $nvalues-->0 && $p<$max )
1220
+ {
1221
+ list(,$val) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
1222
+ $attrvals[$attr][] = sprintf ( "%u", $val );
1223
+ }
1224
+ } else
1225
+ {
1226
+ $attrvals[$attr] = sprintf ( "%u", $val );
1227
+ }
1228
+ }
1229
+
1230
+ if ( $this->_arrayresult )
1231
+ $result["matches"][$idx]["attrs"] = $attrvals;
1232
+ else
1233
+ $result["matches"][$doc]["attrs"] = $attrvals;
1234
+ }
1235
+
1236
+ list ( $total, $total_found, $msecs, $words ) =
1237
+ array_values ( unpack ( "N*N*N*N*", substr ( $response, $p, 16 ) ) );
1238
+ $result["total"] = sprintf ( "%u", $total );
1239
+ $result["total_found"] = sprintf ( "%u", $total_found );
1240
+ $result["time"] = sprintf ( "%.3f", $msecs/1000 );
1241
+ $p += 16;
1242
+
1243
+ while ( $words-->0 && $p<$max )
1244
+ {
1245
+ list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
1246
+ $word = substr ( $response, $p, $len ); $p += $len;
1247
+ list ( $docs, $hits ) = array_values ( unpack ( "N*N*", substr ( $response, $p, 8 ) ) ); $p += 8;
1248
+ $result["words"][$word] = array (
1249
+ "docs"=>sprintf ( "%u", $docs ),
1250
+ "hits"=>sprintf ( "%u", $hits ) );
1251
+ }
1252
+ }
1253
+
1254
+ $this->_MBPop ();
1255
+ return $results;
1256
+ }
1257
+
1258
+ /////////////////////////////////////////////////////////////////////////////
1259
+ // excerpts generation
1260
+ /////////////////////////////////////////////////////////////////////////////
1261
+
1262
+ /// connect to searchd server, and generate exceprts (snippets)
1263
+ /// of given documents for given query. returns false on failure,
1264
+ /// an array of snippets on success
1265
+ function BuildExcerpts ( $docs, $index, $words, $opts=array() )
1266
+ {
1267
+ assert ( is_array($docs) );
1268
+ assert ( is_string($index) );
1269
+ assert ( is_string($words) );
1270
+ assert ( is_array($opts) );
1271
+
1272
+ $this->_MBPush ();
1273
+
1274
+ if (!( $fp = $this->_Connect() ))
1275
+ {
1276
+ $this->_MBPop();
1277
+ return false;
1278
+ }
1279
+
1280
+ /////////////////
1281
+ // fixup options
1282
+ /////////////////
1283
+
1284
+ if ( !isset($opts["before_match"]) ) $opts["before_match"] = "<b>";
1285
+ if ( !isset($opts["after_match"]) ) $opts["after_match"] = "</b>";
1286
+ if ( !isset($opts["chunk_separator"]) ) $opts["chunk_separator"] = " ... ";
1287
+ if ( !isset($opts["limit"]) ) $opts["limit"] = 256;
1288
+ if ( !isset($opts["around"]) ) $opts["around"] = 5;
1289
+ if ( !isset($opts["exact_phrase"]) ) $opts["exact_phrase"] = false;
1290
+ if ( !isset($opts["single_passage"]) ) $opts["single_passage"] = false;
1291
+ if ( !isset($opts["use_boundaries"]) ) $opts["use_boundaries"] = false;
1292
+ if ( !isset($opts["weight_order"]) ) $opts["weight_order"] = false;
1293
+
1294
+ /////////////////
1295
+ // build request
1296
+ /////////////////
1297
+
1298
+ // v.1.0 req
1299
+ $flags = 1; // remove spaces
1300
+ if ( $opts["exact_phrase"] ) $flags |= 2;
1301
+ if ( $opts["single_passage"] ) $flags |= 4;
1302
+ if ( $opts["use_boundaries"] ) $flags |= 8;
1303
+ if ( $opts["weight_order"] ) $flags |= 16;
1304
+ $req = pack ( "NN", 0, $flags ); // mode=0, flags=$flags
1305
+ $req .= pack ( "N", strlen($index) ) . $index; // req index
1306
+ $req .= pack ( "N", strlen($words) ) . $words; // req words
1307
+
1308
+ // options
1309
+ $req .= pack ( "N", strlen($opts["before_match"]) ) . $opts["before_match"];
1310
+ $req .= pack ( "N", strlen($opts["after_match"]) ) . $opts["after_match"];
1311
+ $req .= pack ( "N", strlen($opts["chunk_separator"]) ) . $opts["chunk_separator"];
1312
+ $req .= pack ( "N", (int)$opts["limit"] );
1313
+ $req .= pack ( "N", (int)$opts["around"] );
1314
+
1315
+ // documents
1316
+ $req .= pack ( "N", count($docs) );
1317
+ foreach ( $docs as $doc )
1318
+ {
1319
+ assert ( is_string($doc) );
1320
+ $req .= pack ( "N", strlen($doc) ) . $doc;
1321
+ }
1322
+
1323
+ ////////////////////////////
1324
+ // send query, get response
1325
+ ////////////////////////////
1326
+
1327
+ $len = strlen($req);
1328
+ $req = pack ( "nnN", SEARCHD_COMMAND_EXCERPT, VER_COMMAND_EXCERPT, $len ) . $req; // add header
1329
+ if ( !( $this->_Send ( $fp, $req, $len+8 ) ) ||
1330
+ !( $response = $this->_GetResponse ( $fp, VER_COMMAND_EXCERPT ) ) )
1331
+ {
1332
+ $this->_MBPop ();
1333
+ return false;
1334
+ }
1335
+
1336
+ //////////////////
1337
+ // parse response
1338
+ //////////////////
1339
+
1340
+ $pos = 0;
1341
+ $res = array ();
1342
+ $rlen = strlen($response);
1343
+ for ( $i=0; $i<count($docs); $i++ )
1344
+ {
1345
+ list(,$len) = unpack ( "N*", substr ( $response, $pos, 4 ) );
1346
+ $pos += 4;
1347
+
1348
+ if ( $pos+$len > $rlen )
1349
+ {
1350
+ $this->_error = "incomplete reply";
1351
+ $this->_MBPop ();
1352
+ return false;
1353
+ }
1354
+ $res[] = $len ? substr ( $response, $pos, $len ) : "";
1355
+ $pos += $len;
1356
+ }
1357
+
1358
+ $this->_MBPop ();
1359
+ return $res;
1360
+ }
1361
+
1362
+
1363
+ /////////////////////////////////////////////////////////////////////////////
1364
+ // keyword generation
1365
+ /////////////////////////////////////////////////////////////////////////////
1366
+
1367
+ /// connect to searchd server, and generate keyword list for a given query
1368
+ /// returns false on failure,
1369
+ /// an array of words on success
1370
+ function BuildKeywords ( $query, $index, $hits )
1371
+ {
1372
+ assert ( is_string($query) );
1373
+ assert ( is_string($index) );
1374
+ assert ( is_bool($hits) );
1375
+
1376
+ // Commented out for testing Riddle
1377
+ // $this->_MBPush ();
1378
+ //
1379
+ // if (!( $fp = $this->_Connect() ))
1380
+ // {
1381
+ // $this->_MBPop();
1382
+ // return false;
1383
+ // }
1384
+
1385
+ /////////////////
1386
+ // build request
1387
+ /////////////////
1388
+
1389
+ // v.1.0 req
1390
+ $req = pack ( "N", strlen($query) ) . $query; // req query
1391
+ $req .= pack ( "N", strlen($index) ) . $index; // req index
1392
+ $req .= pack ( "N", (int)$hits );
1393
+
1394
+ // Line for testing Riddle:
1395
+ return $req;
1396
+
1397
+ ////////////////////////////
1398
+ // send query, get response
1399
+ ////////////////////////////
1400
+
1401
+ $len = strlen($req);
1402
+ $req = pack ( "nnN", SEARCHD_COMMAND_KEYWORDS, VER_COMMAND_KEYWORDS, $len ) . $req; // add header
1403
+ if ( !( $this->_Send ( $fp, $req, $len+8 ) ) ||
1404
+ !( $response = $this->_GetResponse ( $fp, VER_COMMAND_KEYWORDS ) ) )
1405
+ {
1406
+ $this->_MBPop ();
1407
+ return false;
1408
+ }
1409
+
1410
+ //////////////////
1411
+ // parse response
1412
+ //////////////////
1413
+
1414
+ $pos = 0;
1415
+ $res = array ();
1416
+ $rlen = strlen($response);
1417
+ list(,$nwords) = unpack ( "N*", substr ( $response, $pos, 4 ) );
1418
+ $pos += 4;
1419
+ for ( $i=0; $i<$nwords; $i++ )
1420
+ {
1421
+ list(,$len) = unpack ( "N*", substr ( $response, $pos, 4 ) ); $pos += 4;
1422
+ $tokenized = $len ? substr ( $response, $pos, $len ) : "";
1423
+ $pos += $len;
1424
+
1425
+ list(,$len) = unpack ( "N*", substr ( $response, $pos, 4 ) ); $pos += 4;
1426
+ $normalized = $len ? substr ( $response, $pos, $len ) : "";
1427
+ $pos += $len;
1428
+
1429
+ $res[] = array ( "tokenized"=>$tokenized, "normalized"=>$normalized );
1430
+
1431
+ if ( $hits )
1432
+ {
1433
+ list($ndocs,$nhits) = array_values ( unpack ( "N*N*", substr ( $response, $pos, 8 ) ) );
1434
+ $pos += 8;
1435
+ $res [$i]["docs"] = $ndocs;
1436
+ $res [$i]["hits"] = $nhits;
1437
+ }
1438
+
1439
+ if ( $pos > $rlen )
1440
+ {
1441
+ $this->_error = "incomplete reply";
1442
+ $this->_MBPop ();
1443
+ return false;
1444
+ }
1445
+ }
1446
+
1447
+ $this->_MBPop ();
1448
+ return $res;
1449
+ }
1450
+
1451
+ function EscapeString ( $string )
1452
+ {
1453
+ $from = array ( '\\', '(',')','|','-','!','@','~','"','&', '/', '^', '$', '=' );
1454
+ $to = array ( '\\\\', '\(','\)','\|','\-','\!','\@','\~','\"', '\&', '\/', '\^', '\$', '\=' );
1455
+
1456
+ return str_replace ( $from, $to, $string );
1457
+ }
1458
+
1459
+ /////////////////////////////////////////////////////////////////////////////
1460
+ // attribute updates
1461
+ /////////////////////////////////////////////////////////////////////////////
1462
+
1463
+ /// batch update given attributes in given rows in given indexes
1464
+ /// returns amount of updated documents (0 or more) on success, or -1 on failure
1465
+ function UpdateAttributes ( $index, $attrs, $values, $mva=false )
1466
+ {
1467
+ // verify everything
1468
+ assert ( is_string($index) );
1469
+ assert ( is_bool($mva) );
1470
+
1471
+ assert ( is_array($attrs) );
1472
+ foreach ( $attrs as $attr )
1473
+ assert ( is_string($attr) );
1474
+
1475
+ assert ( is_array($values) );
1476
+ foreach ( $values as $id=>$entry )
1477
+ {
1478
+ assert ( is_numeric($id) );
1479
+ assert ( is_array($entry) );
1480
+ assert ( count($entry)==count($attrs) );
1481
+ foreach ( $entry as $v )
1482
+ {
1483
+ if ( $mva )
1484
+ {
1485
+ assert ( is_array($v) );
1486
+ foreach ( $v as $vv )
1487
+ assert ( is_int($vv) );
1488
+ } else
1489
+ assert ( is_int($v) );
1490
+ }
1491
+ }
1492
+
1493
+ // build request
1494
+ $req = pack ( "N", strlen($index) ) . $index;
1495
+
1496
+ $req .= pack ( "N", count($attrs) );
1497
+ foreach ( $attrs as $attr )
1498
+ {
1499
+ $req .= pack ( "N", strlen($attr) ) . $attr;
1500
+ $req .= pack ( "N", $mva ? 1 : 0 );
1501
+ }
1502
+
1503
+ $req .= pack ( "N", count($values) );
1504
+ foreach ( $values as $id=>$entry )
1505
+ {
1506
+ $req .= sphPackU64 ( $id );
1507
+ foreach ( $entry as $v )
1508
+ {
1509
+ $req .= pack ( "N", $mva ? count($v) : $v );
1510
+ if ( $mva )
1511
+ foreach ( $v as $vv )
1512
+ $req .= pack ( "N", $vv );
1513
+ }
1514
+ }
1515
+
1516
+ // Line for testing Riddle:
1517
+ return $req;
1518
+
1519
+ // connect, send query, get response
1520
+ if (!( $fp = $this->_Connect() ))
1521
+ return -1;
1522
+
1523
+ $len = strlen($req);
1524
+ $req = pack ( "nnN", SEARCHD_COMMAND_UPDATE, VER_COMMAND_UPDATE, $len ) . $req; // add header
1525
+ if ( !$this->_Send ( $fp, $req, $len+8 ) )
1526
+ return -1;
1527
+
1528
+ if (!( $response = $this->_GetResponse ( $fp, VER_COMMAND_UPDATE ) ))
1529
+ return -1;
1530
+
1531
+ // parse response
1532
+ list(,$updated) = unpack ( "N*", substr ( $response, 0, 4 ) );
1533
+ return $updated;
1534
+ }
1535
+
1536
+ /////////////////////////////////////////////////////////////////////////////
1537
+ // persistent connections
1538
+ /////////////////////////////////////////////////////////////////////////////
1539
+
1540
+ function Open()
1541
+ {
1542
+ if ( $this->_socket !== false )
1543
+ {
1544
+ $this->_error = 'already connected';
1545
+ return false;
1546
+ }
1547
+ if ( !$fp = $this->_Connect() )
1548
+ return false;
1549
+
1550
+ // command, command version = 0, body length = 4, body = 1
1551
+ $req = pack ( "nnNN", SEARCHD_COMMAND_PERSIST, 0, 4, 1 );
1552
+ if ( !$this->_Send ( $fp, $req, 12 ) )
1553
+ return false;
1554
+
1555
+ $this->_socket = $fp;
1556
+ return true;
1557
+ }
1558
+
1559
+ function Close()
1560
+ {
1561
+ if ( $this->_socket === false )
1562
+ {
1563
+ $this->_error = 'not connected';
1564
+ return false;
1565
+ }
1566
+
1567
+ fclose ( $this->_socket );
1568
+ $this->_socket = false;
1569
+
1570
+ return true;
1571
+ }
1572
+
1573
+ //////////////////////////////////////////////////////////////////////////
1574
+ // status
1575
+ //////////////////////////////////////////////////////////////////////////
1576
+
1577
+ function Status ()
1578
+ {
1579
+ $this->_MBPush ();
1580
+ if (!( $fp = $this->_Connect() ))
1581
+ {
1582
+ $this->_MBPop();
1583
+ return false;
1584
+ }
1585
+
1586
+ $req = pack ( "nnNN", SEARCHD_COMMAND_STATUS, VER_COMMAND_STATUS, 4, 1 ); // len=4, body=1
1587
+ if ( !( $this->_Send ( $fp, $req, 12 ) ) ||
1588
+ !( $response = $this->_GetResponse ( $fp, VER_COMMAND_STATUS ) ) )
1589
+ {
1590
+ $this->_MBPop ();
1591
+ return false;
1592
+ }
1593
+
1594
+ $res = substr ( $response, 4 ); // just ignore length, error handling, etc
1595
+ $p = 0;
1596
+ list ( $rows, $cols ) = array_values ( unpack ( "N*N*", substr ( $response, $p, 8 ) ) ); $p += 8;
1597
+
1598
+ $res = array();
1599
+ for ( $i=0; $i<$rows; $i++ )
1600
+ for ( $j=0; $j<$cols; $j++ )
1601
+ {
1602
+ list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
1603
+ $res[$i][] = substr ( $response, $p, $len ); $p += $len;
1604
+ }
1605
+
1606
+ $this->_MBPop ();
1607
+ return $res;
1608
+ }
1609
+
1610
+ // Added for Riddle - code is taken from AddQuery
1611
+ function FilterOutput()
1612
+ {
1613
+ $req = "";
1614
+ foreach ( $this->_filters as $filter )
1615
+ {
1616
+ $req .= pack ( "N", strlen($filter["attr"]) ) . $filter["attr"];
1617
+ $req .= pack ( "N", $filter["type"] );
1618
+ switch ( $filter["type"] )
1619
+ {
1620
+ case SPH_FILTER_VALUES:
1621
+ $req .= pack ( "N", count($filter["values"]) );
1622
+ foreach ( $filter["values"] as $value )
1623
+ $req .= sphPackI64 ( $value );
1624
+ break;
1625
+
1626
+ case SPH_FILTER_RANGE:
1627
+ $req .= sphPackI64 ( $filter["min"] ) . sphPackI64 ( $filter["max"] );
1628
+ break;
1629
+
1630
+ case SPH_FILTER_FLOATRANGE:
1631
+ $req .= $this->_PackFloat ( $filter["min"] ) . $this->_PackFloat ( $filter["max"] );
1632
+ break;
1633
+
1634
+ default:
1635
+ assert ( 0 && "internal error: unhandled filter type" );
1636
+ }
1637
+ $req .= pack ( "N", $filter["exclude"] );
1638
+ }
1639
+
1640
+ return $req;
1641
+ }
1642
+ }
1643
+
1644
+ //
1645
+ // $Id: sphinxapi.php 1775 2009-04-06 22:15:58Z shodan $
1646
+ //