riddle 1.0.3 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. data/README.textile +1 -0
  2. data/lib/riddle.rb +30 -4
  3. data/lib/riddle/0.9.8.rb +1 -0
  4. data/lib/riddle/0.9.9.rb +2 -0
  5. data/lib/riddle/auto_version.rb +11 -0
  6. data/lib/riddle/client.rb +2 -0
  7. data/lib/riddle/configuration.rb +3 -1
  8. data/lib/riddle/configuration/sql_source.rb +13 -0
  9. data/lib/riddle/controller.rb +29 -2
  10. data/spec/functional/excerpt_spec.rb +2 -2
  11. data/spec/functional/status_spec.rb +1 -1
  12. data/spec/riddle/auto_version_spec.rb +24 -0
  13. data/spec/riddle/client_spec.rb +11 -0
  14. data/spec/riddle/configuration_spec.rb +11 -0
  15. data/spec/riddle/controller_spec.rb +29 -0
  16. data/spec/riddle_spec.rb +27 -0
  17. data/spec/spec_helper.rb +1 -4
  18. data/spec/unit/client_spec.rb +1 -1
  19. data/spec/unit/configuration/searchd_spec.rb +2 -2
  20. data/spec/unit/configuration/sql_source_spec.rb +21 -0
  21. metadata +8 -48
  22. data/spec/fixtures/data/anchor.bin +0 -0
  23. data/spec/fixtures/data/any.bin +0 -0
  24. data/spec/fixtures/data/boolean.bin +0 -0
  25. data/spec/fixtures/data/comment.bin +0 -0
  26. data/spec/fixtures/data/distinct.bin +0 -0
  27. data/spec/fixtures/data/field_weights.bin +0 -0
  28. data/spec/fixtures/data/filter.bin +0 -0
  29. data/spec/fixtures/data/filter_array.bin +0 -0
  30. data/spec/fixtures/data/filter_array_exclude.bin +0 -0
  31. data/spec/fixtures/data/filter_boolean.bin +0 -0
  32. data/spec/fixtures/data/filter_floats.bin +0 -0
  33. data/spec/fixtures/data/filter_floats_exclude.bin +0 -0
  34. data/spec/fixtures/data/filter_range.bin +0 -0
  35. data/spec/fixtures/data/filter_range_exclude.bin +0 -0
  36. data/spec/fixtures/data/group.bin +0 -0
  37. data/spec/fixtures/data/index.bin +0 -0
  38. data/spec/fixtures/data/index_weights.bin +0 -0
  39. data/spec/fixtures/data/keywords_with_hits.bin +0 -0
  40. data/spec/fixtures/data/keywords_without_hits.bin +0 -0
  41. data/spec/fixtures/data/overrides.bin +0 -0
  42. data/spec/fixtures/data/phrase.bin +0 -0
  43. data/spec/fixtures/data/rank_mode.bin +0 -0
  44. data/spec/fixtures/data/select.bin +0 -0
  45. data/spec/fixtures/data/simple.bin +0 -0
  46. data/spec/fixtures/data/sort.bin +0 -0
  47. data/spec/fixtures/data/update_simple.bin +0 -0
  48. data/spec/fixtures/data/weights.bin +0 -0
  49. data/spec/fixtures/data_generator.0.9.8.php +0 -208
  50. data/spec/fixtures/data_generator.0.9.9.php +0 -225
  51. data/spec/fixtures/sphinx/configuration.erb +0 -38
  52. data/spec/fixtures/sphinx/people.spa +0 -0
  53. data/spec/fixtures/sphinx/people.spd +0 -0
  54. data/spec/fixtures/sphinx/people.sph +0 -0
  55. data/spec/fixtures/sphinx/people.spi +0 -0
  56. data/spec/fixtures/sphinx/people.spk +0 -0
  57. data/spec/fixtures/sphinx/people.spm +0 -0
  58. data/spec/fixtures/sphinx/people.spp +0 -0
  59. data/spec/fixtures/sphinx/searchd.log +0 -4430
  60. data/spec/fixtures/sphinx/searchd.query.log +0 -1234
  61. data/spec/fixtures/sphinx/spec.conf +0 -38
  62. data/spec/fixtures/sphinxapi.0.9.8.php +0 -1228
  63. data/spec/fixtures/sphinxapi.0.9.9.php +0 -1646
  64. data/spec/fixtures/sql/conf.example.yml +0 -3
  65. data/spec/fixtures/sql/conf.yml +0 -3
  66. data/spec/fixtures/sql/data.sql +0 -25000
  67. data/spec/fixtures/sql/structure.sql +0 -16
@@ -1,1646 +0,0 @@
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
- //