1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
// |
4
|
|
|
// $Id$ |
5
|
|
|
// |
6
|
|
|
|
7
|
|
|
// |
8
|
|
|
// Copyright (c) 2001-2015, Andrew Aksyonoff |
9
|
|
|
// Copyright (c) 2008-2015, Sphinx Technologies Inc |
10
|
|
|
// All rights reserved |
11
|
|
|
// |
12
|
|
|
// This program is free software; you can redistribute it and/or modify |
13
|
|
|
// it under the terms of the GNU Library General Public License. You should |
14
|
|
|
// have received a copy of the LGPL license along with this program; if you |
15
|
|
|
// did not, you can find it at http://www.gnu.org/ |
16
|
|
|
// |
17
|
|
|
// WARNING!!! |
18
|
|
|
// |
19
|
|
|
// As of 2015, we strongly recommend to use either SphinxQL or REST APIs |
20
|
|
|
// rather than the native SphinxAPI. |
21
|
|
|
// |
22
|
|
|
// While both the native SphinxAPI protocol and the existing APIs will |
23
|
|
|
// continue to exist, and perhaps should not even break (too much), exposing |
24
|
|
|
// all the new features via multiple different native API implementations |
25
|
|
|
// is too much of a support complication for us. |
26
|
|
|
// |
27
|
|
|
// That said, you're welcome to overtake the maintenance of any given |
28
|
|
|
// official API, and remove this warning ;) |
29
|
|
|
// |
30
|
|
|
|
31
|
|
|
///////////////////////////////////////////////////////////////////////////// |
32
|
|
|
// PHP version of Sphinx searchd client (PHP API) |
33
|
|
|
///////////////////////////////////////////////////////////////////////////// |
34
|
|
|
|
35
|
|
|
// known searchd commands |
36
|
|
|
define('SEARCHD_COMMAND_SEARCH', 0); |
37
|
|
|
define('SEARCHD_COMMAND_EXCERPT', 1); |
38
|
|
|
define('SEARCHD_COMMAND_UPDATE', 2); |
39
|
|
|
define('SEARCHD_COMMAND_KEYWORDS', 3); |
40
|
|
|
define('SEARCHD_COMMAND_PERSIST', 4); |
41
|
|
|
define('SEARCHD_COMMAND_STATUS', 5); |
42
|
|
|
define('SEARCHD_COMMAND_FLUSHATTRS', 7); |
43
|
|
|
|
44
|
|
|
// current client-side command implementation versions |
45
|
|
|
define('VER_COMMAND_SEARCH', 0x11E); |
46
|
|
|
define('VER_COMMAND_EXCERPT', 0x104); |
47
|
|
|
define('VER_COMMAND_UPDATE', 0x103); |
48
|
|
|
define('VER_COMMAND_KEYWORDS', 0x100); |
49
|
|
|
define('VER_COMMAND_STATUS', 0x101); |
50
|
|
|
define('VER_COMMAND_QUERY', 0x100); |
51
|
|
|
define('VER_COMMAND_FLUSHATTRS', 0x100); |
52
|
|
|
|
53
|
|
|
// known searchd status codes |
54
|
|
|
define('SEARCHD_OK', 0); |
55
|
|
|
define('SEARCHD_ERROR', 1); |
56
|
|
|
define('SEARCHD_RETRY', 2); |
57
|
|
|
define('SEARCHD_WARNING', 3); |
58
|
|
|
|
59
|
|
|
// known match modes |
60
|
|
|
define('SPH_MATCH_ALL', 0); |
61
|
|
|
define('SPH_MATCH_ANY', 1); |
62
|
|
|
define('SPH_MATCH_PHRASE', 2); |
63
|
|
|
define('SPH_MATCH_BOOLEAN', 3); |
64
|
|
|
define('SPH_MATCH_EXTENDED', 4); |
65
|
|
|
define('SPH_MATCH_FULLSCAN', 5); |
66
|
|
|
define('SPH_MATCH_EXTENDED2', 6); // extended engine V2 (TEMPORARY, WILL BE REMOVED) |
67
|
|
|
|
68
|
|
|
// known ranking modes (ext2 only) |
69
|
|
|
define('SPH_RANK_PROXIMITY_BM25', 0); // default mode, phrase proximity major factor and BM25 minor one |
70
|
|
|
define('SPH_RANK_BM25', 1); // statistical mode, BM25 ranking only (faster but worse quality) |
71
|
|
|
define('SPH_RANK_NONE', 2); // no ranking, all matches get a weight of 1 |
72
|
|
|
define('SPH_RANK_WORDCOUNT', 3); // simple word-count weighting, rank is a weighted sum of per-field keyword occurence counts |
73
|
|
|
define('SPH_RANK_PROXIMITY', 4); |
74
|
|
|
define('SPH_RANK_MATCHANY', 5); |
75
|
|
|
define('SPH_RANK_FIELDMASK', 6); |
76
|
|
|
define('SPH_RANK_SPH04', 7); |
77
|
|
|
define('SPH_RANK_EXPR', 8); |
78
|
|
|
define('SPH_RANK_TOTAL', 9); |
79
|
|
|
|
80
|
|
|
// known sort modes |
81
|
|
|
define('SPH_SORT_RELEVANCE', 0); |
82
|
|
|
define('SPH_SORT_ATTR_DESC', 1); |
83
|
|
|
define('SPH_SORT_ATTR_ASC', 2); |
84
|
|
|
define('SPH_SORT_TIME_SEGMENTS', 3); |
85
|
|
|
define('SPH_SORT_EXTENDED', 4); |
86
|
|
|
define('SPH_SORT_EXPR', 5); |
87
|
|
|
|
88
|
|
|
// known filter types |
89
|
|
|
define('SPH_FILTER_VALUES', 0); |
90
|
|
|
define('SPH_FILTER_RANGE', 1); |
91
|
|
|
define('SPH_FILTER_FLOATRANGE', 2); |
92
|
|
|
define('SPH_FILTER_STRING', 3); |
93
|
|
|
|
94
|
|
|
// known attribute types |
95
|
|
|
define('SPH_ATTR_INTEGER', 1); |
96
|
|
|
define('SPH_ATTR_TIMESTAMP', 2); |
97
|
|
|
define('SPH_ATTR_ORDINAL', 3); |
98
|
|
|
define('SPH_ATTR_BOOL', 4); |
99
|
|
|
define('SPH_ATTR_FLOAT', 5); |
100
|
|
|
define('SPH_ATTR_BIGINT', 6); |
101
|
|
|
define('SPH_ATTR_STRING', 7); |
102
|
|
|
define('SPH_ATTR_FACTORS', 1001); |
103
|
|
|
define('SPH_ATTR_MULTI', 0x40000001); |
104
|
|
|
define('SPH_ATTR_MULTI64', 0x40000002); |
105
|
|
|
|
106
|
|
|
// known grouping functions |
107
|
|
|
define('SPH_GROUPBY_DAY', 0); |
108
|
|
|
define('SPH_GROUPBY_WEEK', 1); |
109
|
|
|
define('SPH_GROUPBY_MONTH', 2); |
110
|
|
|
define('SPH_GROUPBY_YEAR', 3); |
111
|
|
|
define('SPH_GROUPBY_ATTR', 4); |
112
|
|
|
define('SPH_GROUPBY_ATTRPAIR', 5); |
113
|
|
|
|
114
|
|
|
// important properties of PHP's integers: |
115
|
|
|
// - always signed (one bit short of PHP_INT_SIZE) |
116
|
|
|
// - conversion from string to int is saturated |
117
|
|
|
// - float is double |
118
|
|
|
// - div converts arguments to floats |
119
|
|
|
// - mod converts arguments to ints |
120
|
|
|
|
121
|
|
|
// the packing code below works as follows: |
122
|
|
|
// - when we got an int, just pack it |
123
|
|
|
// if performance is a problem, this is the branch users should aim for |
124
|
|
|
// |
125
|
|
|
// - otherwise, we got a number in string form |
126
|
|
|
// this might be due to different reasons, but we assume that this is |
127
|
|
|
// because it didn't fit into PHP int |
128
|
|
|
// |
129
|
|
|
// - factor the string into high and low ints for packing |
130
|
|
|
// - if we have bcmath, then it is used |
131
|
|
|
// - if we don't, we have to do it manually (this is the fun part) |
132
|
|
|
// |
133
|
|
|
// - x64 branch does factoring using ints |
134
|
|
|
// - x32 (ab)uses floats, since we can't fit unsigned 32-bit number into an int |
135
|
|
|
// |
136
|
|
|
// unpacking routines are pretty much the same. |
137
|
|
|
// - return ints if we can |
138
|
|
|
// - otherwise format number into a string |
139
|
|
|
|
140
|
|
|
/** |
141
|
|
|
* Pack 64-bit signed |
142
|
|
|
* |
143
|
|
|
* @param int $value |
144
|
|
|
* |
145
|
|
|
* @return string |
146
|
|
|
*/ |
147
|
|
|
function sphPackI64($value) |
148
|
|
|
{ |
149
|
|
|
assert(is_numeric($value)); |
150
|
|
|
|
151
|
|
|
// x64 |
152
|
|
|
if (PHP_INT_SIZE >= 8) { |
153
|
|
|
$value = (int)$value; |
154
|
|
|
return pack('NN', $value >> 32, $value & 0xFFFFFFFF); |
155
|
|
|
} |
156
|
|
|
|
157
|
|
|
// x32, int |
158
|
|
|
if (is_int($value)) { |
159
|
|
|
return pack('NN', $value < 0 ? -1 : 0, $value); |
160
|
|
|
} |
161
|
|
|
|
162
|
|
|
// x32, bcmath |
163
|
|
|
if (function_exists('bcmul')) { |
164
|
|
|
if (bccomp($value, 0) == -1) { |
165
|
|
|
$value = bcadd('18446744073709551616', $value); |
166
|
|
|
} |
167
|
|
|
$h = bcdiv($value, '4294967296', 0); |
168
|
|
|
$l = bcmod($value, '4294967296'); |
169
|
|
|
return pack('NN', (float)$h, (float)$l); // conversion to float is intentional; int would lose 31st bit |
170
|
|
|
} |
171
|
|
|
|
172
|
|
|
// x32, no-bcmath |
173
|
|
|
$p = max(0, strlen($value) - 13); |
174
|
|
|
$lo = abs((float)substr($value, $p)); |
175
|
|
|
$hi = abs((float)substr($value, 0, $p)); |
176
|
|
|
|
177
|
|
|
$m = $lo + $hi * 1316134912.0; // (10 ^ 13) % (1 << 32) = 1316134912 |
|
|
|
|
178
|
|
|
$q = floor($m / 4294967296.0); |
179
|
|
|
$l = $m - ($q * 4294967296.0); |
180
|
|
|
$h = $hi * 2328.0 + $q; // (10 ^ 13) / (1 << 32) = 2328 |
|
|
|
|
181
|
|
|
|
182
|
|
|
if ($value < 0) { |
183
|
|
|
if ($l == 0) { |
184
|
|
|
$h = 4294967296.0 - $h; |
185
|
|
|
} else { |
186
|
|
|
$h = 4294967295.0 - $h; |
187
|
|
|
$l = 4294967296.0 - $l; |
188
|
|
|
} |
189
|
|
|
} |
190
|
|
|
return pack('NN', $h, $l); |
191
|
|
|
} |
192
|
|
|
|
193
|
|
|
/** |
194
|
|
|
* Ppack 64-bit unsigned |
195
|
|
|
* |
196
|
|
|
* @param int $value |
197
|
|
|
* |
198
|
|
|
* @return string |
199
|
|
|
*/ |
200
|
|
|
function sphPackU64($value) |
201
|
|
|
{ |
202
|
|
|
assert(is_numeric($value)); |
203
|
|
|
|
204
|
|
|
// x64 |
205
|
|
|
if (PHP_INT_SIZE >= 8) { |
206
|
|
|
assert($value >= 0); |
207
|
|
|
|
208
|
|
|
// x64, int |
209
|
|
|
if (is_int($value)) { |
210
|
|
|
return pack('NN', $value >> 32, $value & 0xFFFFFFFF); |
211
|
|
|
} |
212
|
|
|
|
213
|
|
|
// x64, bcmath |
214
|
|
View Code Duplication |
if (function_exists('bcmul')) { |
|
|
|
|
215
|
|
|
$h = bcdiv($value, 4294967296, 0); |
216
|
|
|
$l = bcmod($value, 4294967296); |
217
|
|
|
return pack('NN', $h, $l); |
218
|
|
|
} |
219
|
|
|
|
220
|
|
|
// x64, no-bcmath |
221
|
|
|
$p = max(0, strlen($value) - 13); |
222
|
|
|
$lo = (int)substr($value, $p); |
223
|
|
|
$hi = (int)substr($value, 0, $p); |
224
|
|
|
|
225
|
|
|
$m = $lo + $hi * 1316134912; |
226
|
|
|
$l = $m % 4294967296; |
227
|
|
|
$h = $hi * 2328 + (int)($m / 4294967296); |
228
|
|
|
|
229
|
|
|
return pack('NN', $h, $l); |
230
|
|
|
} |
231
|
|
|
|
232
|
|
|
// x32, int |
233
|
|
|
if (is_int($value)) { |
234
|
|
|
return pack('NN', 0, $value); |
235
|
|
|
} |
236
|
|
|
|
237
|
|
|
// x32, bcmath |
238
|
|
View Code Duplication |
if (function_exists('bcmul')) { |
|
|
|
|
239
|
|
|
$h = bcdiv($value, '4294967296', 0); |
240
|
|
|
$l = bcmod($value, '4294967296'); |
241
|
|
|
return pack('NN', (float)$h, (float)$l); // conversion to float is intentional; int would lose 31st bit |
242
|
|
|
} |
243
|
|
|
|
244
|
|
|
// x32, no-bcmath |
245
|
|
|
$p = max(0, strlen($value) - 13); |
246
|
|
|
$lo = (float)substr($value, $p); |
247
|
|
|
$hi = (float)substr($value, 0, $p); |
248
|
|
|
|
249
|
|
|
$m = $lo + $hi * 1316134912.0; |
250
|
|
|
$q = floor($m / 4294967296.0); |
251
|
|
|
$l = $m - ($q * 4294967296.0); |
252
|
|
|
$h = $hi * 2328.0 + $q; |
253
|
|
|
|
254
|
|
|
return pack('NN', $h, $l); |
255
|
|
|
} |
256
|
|
|
|
257
|
|
|
/** |
258
|
|
|
* Unpack 64-bit unsigned |
259
|
|
|
* |
260
|
|
|
* @param string $value |
261
|
|
|
* |
262
|
|
|
* @return string |
263
|
|
|
*/ |
264
|
|
|
function sphUnpackU64($value) |
265
|
|
|
{ |
266
|
|
|
list($hi, $lo) = array_values(unpack('N*N*', $value)); |
267
|
|
|
|
268
|
|
|
if (PHP_INT_SIZE >= 8) { |
269
|
|
|
if ($hi < 0) { // because php 5.2.2 to 5.2.5 is totally fucked up again |
270
|
|
|
$hi += 1 << 32; |
271
|
|
|
} |
272
|
|
|
if ($lo < 0) { |
273
|
|
|
$lo += 1 << 32; |
274
|
|
|
} |
275
|
|
|
|
276
|
|
|
// x64, int |
277
|
|
|
if ($hi <= 2147483647) { |
278
|
|
|
return ($hi << 32) + $lo; |
279
|
|
|
} |
280
|
|
|
|
281
|
|
|
// x64, bcmath |
282
|
|
|
if (function_exists('bcmul')) { |
283
|
|
|
return bcadd($lo, bcmul($hi, '4294967296')); |
284
|
|
|
} |
285
|
|
|
|
286
|
|
|
// x64, no-bcmath |
287
|
|
|
$C = 100000; |
288
|
|
|
$h = ((int)($hi / $C) << 32) + (int)($lo / $C); |
289
|
|
|
$l = (($hi % $C) << 32) + ($lo % $C); |
290
|
|
|
if ($l > $C) { |
291
|
|
|
$h += (int)($l / $C); |
292
|
|
|
$l = $l % $C; |
293
|
|
|
} |
294
|
|
|
|
295
|
|
|
if ($h == 0) { |
296
|
|
|
return $l; |
297
|
|
|
} |
298
|
|
|
return sprintf('%d%05d', $h, $l); |
299
|
|
|
} |
300
|
|
|
|
301
|
|
|
// x32, int |
302
|
|
|
if ($hi == 0) { |
303
|
|
|
if ($lo > 0) { |
304
|
|
|
return $lo; |
305
|
|
|
} |
306
|
|
|
return sprintf('%u', $lo); |
307
|
|
|
} |
308
|
|
|
|
309
|
|
|
$hi = sprintf('%u', $hi); |
310
|
|
|
$lo = sprintf('%u', $lo); |
311
|
|
|
|
312
|
|
|
// x32, bcmath |
313
|
|
|
if (function_exists('bcmul')) { |
314
|
|
|
return bcadd($lo, bcmul($hi, '4294967296')); |
315
|
|
|
} |
316
|
|
|
|
317
|
|
|
// x32, no-bcmath |
318
|
|
|
$hi = (float)$hi; |
319
|
|
|
$lo = (float)$lo; |
320
|
|
|
|
321
|
|
|
$q = floor($hi / 10000000.0); |
322
|
|
|
$r = $hi - $q * 10000000.0; |
323
|
|
|
$m = $lo + $r * 4967296.0; |
324
|
|
|
$mq = floor($m / 10000000.0); |
325
|
|
|
$l = $m - $mq * 10000000.0; |
326
|
|
|
$h = $q * 4294967296.0 + $r * 429.0 + $mq; |
327
|
|
|
|
328
|
|
|
$h = sprintf('%.0f', $h); |
329
|
|
|
$l = sprintf('%07.0f', $l); |
330
|
|
|
if ($h == '0') { |
331
|
|
|
return sprintf('%.0f', (float)$l); |
332
|
|
|
} |
333
|
|
|
return $h . $l; |
334
|
|
|
} |
335
|
|
|
|
336
|
|
|
/** |
337
|
|
|
* Unpack 64-bit signed |
338
|
|
|
* |
339
|
|
|
* @param string $value |
340
|
|
|
* |
341
|
|
|
* @return string |
342
|
|
|
*/ |
343
|
|
|
function sphUnpackI64($value) |
344
|
|
|
{ |
345
|
|
|
list($hi, $lo) = array_values(unpack('N*N*', $value)); |
346
|
|
|
|
347
|
|
|
// x64 |
348
|
|
|
if (PHP_INT_SIZE >= 8) { |
349
|
|
|
if ($hi < 0) { // because php 5.2.2 to 5.2.5 is totally fucked up again |
350
|
|
|
$hi += 1 << 32; |
351
|
|
|
} |
352
|
|
|
if ($lo < 0) { |
353
|
|
|
$lo += 1 << 32; |
354
|
|
|
} |
355
|
|
|
|
356
|
|
|
return ($hi << 32) + $lo; |
357
|
|
|
} |
358
|
|
|
|
359
|
|
|
if ($hi == 0) { // x32, int |
360
|
|
|
if ($lo > 0) { |
361
|
|
|
return $lo; |
362
|
|
|
} |
363
|
|
|
return sprintf('%u', $lo); |
364
|
|
|
} elseif ($hi == -1) { // x32, int |
365
|
|
|
if ($lo < 0) { |
366
|
|
|
return $lo; |
367
|
|
|
} |
368
|
|
|
return sprintf('%.0f', $lo - 4294967296.0); |
369
|
|
|
} |
370
|
|
|
|
371
|
|
|
$neg = ''; |
372
|
|
|
$c = 0; |
373
|
|
|
if ($hi < 0) { |
374
|
|
|
$hi = ~$hi; |
375
|
|
|
$lo = ~$lo; |
376
|
|
|
$c = 1; |
377
|
|
|
$neg = '-'; |
378
|
|
|
} |
379
|
|
|
|
380
|
|
|
$hi = sprintf('%u', $hi); |
381
|
|
|
$lo = sprintf('%u', $lo); |
382
|
|
|
|
383
|
|
|
// x32, bcmath |
384
|
|
|
if (function_exists('bcmul')) { |
385
|
|
|
return $neg . bcadd(bcadd($lo, bcmul($hi, '4294967296')), $c); |
386
|
|
|
} |
387
|
|
|
|
388
|
|
|
// x32, no-bcmath |
389
|
|
|
$hi = (float)$hi; |
390
|
|
|
$lo = (float)$lo; |
391
|
|
|
|
392
|
|
|
$q = floor($hi / 10000000.0); |
393
|
|
|
$r = $hi - $q * 10000000.0; |
394
|
|
|
$m = $lo + $r * 4967296.0; |
395
|
|
|
$mq = floor($m / 10000000.0); |
396
|
|
|
$l = $m - $mq * 10000000.0 + $c; |
397
|
|
|
$h = $q * 4294967296.0 + $r * 429.0 + $mq; |
398
|
|
|
if ($l == 10000000) { |
399
|
|
|
$l = 0; |
400
|
|
|
$h += 1; |
401
|
|
|
} |
402
|
|
|
|
403
|
|
|
$h = sprintf('%.0f', $h); |
404
|
|
|
$l = sprintf('%07.0f', $l); |
405
|
|
|
if ($h == '0') { |
406
|
|
|
return $neg . sprintf('%.0f', (float)$l); |
407
|
|
|
} |
408
|
|
|
return $neg . $h . $l; |
409
|
|
|
} |
410
|
|
|
|
411
|
|
|
/** |
412
|
|
|
* @param int $value |
413
|
|
|
* |
414
|
|
|
* @return int|string |
415
|
|
|
*/ |
416
|
|
|
function sphFixUint($value) |
417
|
|
|
{ |
418
|
|
|
if (PHP_INT_SIZE >= 8) { |
419
|
|
|
// x64 route, workaround broken unpack() in 5.2.2+ |
420
|
|
|
if ($value < 0) { |
421
|
|
|
$value += 1 << 32; |
422
|
|
|
} |
423
|
|
|
return $value; |
424
|
|
|
} else { |
425
|
|
|
// x32 route, workaround php signed/unsigned braindamage |
426
|
|
|
return sprintf('%u', $value); |
427
|
|
|
} |
428
|
|
|
} |
429
|
|
|
|
430
|
|
|
/** |
431
|
|
|
* @param int $flag |
432
|
|
|
* @param int $bit |
433
|
|
|
* @param bool $on |
434
|
|
|
* |
435
|
|
|
* @return int |
436
|
|
|
*/ |
437
|
|
|
function sphSetBit($flag, $bit, $on) |
438
|
|
|
{ |
439
|
4 |
|
if ($on) { |
440
|
4 |
|
$flag |= 1 << $bit; |
441
|
4 |
|
} else { |
442
|
|
|
$reset = 16777215 ^ (1 << $bit); |
443
|
|
|
$flag = $flag & $reset; |
444
|
|
|
} |
445
|
4 |
|
return $flag; |
446
|
|
|
} |
447
|
|
|
|
448
|
|
|
|
449
|
|
|
/** |
450
|
|
|
* Sphinx searchd client class |
451
|
|
|
*/ |
452
|
|
|
class SphinxClient |
|
|
|
|
453
|
|
|
{ |
454
|
|
|
/** |
455
|
|
|
* Searchd host |
456
|
|
|
* |
457
|
|
|
* @var string |
458
|
|
|
*/ |
459
|
|
|
protected $host = 'localhost'; |
460
|
|
|
|
461
|
|
|
/** |
462
|
|
|
* Searchd port |
463
|
|
|
* |
464
|
|
|
* @var int |
465
|
|
|
*/ |
466
|
|
|
protected $port = 9312; |
467
|
|
|
|
468
|
|
|
/** |
469
|
|
|
* How many records to seek from result-set start |
470
|
|
|
* |
471
|
|
|
* @var int |
472
|
|
|
*/ |
473
|
|
|
protected $offset = 0; |
474
|
|
|
|
475
|
|
|
/** |
476
|
|
|
* How many records to return from result-set starting at offset |
477
|
|
|
* |
478
|
|
|
* @var int |
479
|
|
|
*/ |
480
|
|
|
protected $limit = 20; |
481
|
|
|
|
482
|
|
|
/** |
483
|
|
|
* Query matching mode |
484
|
|
|
* |
485
|
|
|
* @var int |
486
|
|
|
*/ |
487
|
|
|
protected $mode = SPH_MATCH_EXTENDED2; |
488
|
|
|
|
489
|
|
|
/** |
490
|
|
|
* Per-field weights (default is 1 for all fields) |
491
|
|
|
* |
492
|
|
|
* @var array |
493
|
|
|
*/ |
494
|
|
|
protected $weights = array(); |
495
|
|
|
|
496
|
|
|
/** |
497
|
|
|
* Match sorting mode |
498
|
|
|
* |
499
|
|
|
* @var int |
500
|
|
|
*/ |
501
|
|
|
protected $sort = SPH_SORT_RELEVANCE; |
502
|
|
|
|
503
|
|
|
/** |
504
|
|
|
* Attribute to sort by |
505
|
|
|
* |
506
|
|
|
* @var string |
507
|
|
|
*/ |
508
|
|
|
protected $sort_by = ''; |
509
|
|
|
|
510
|
|
|
/** |
511
|
|
|
* Min ID to match (0 means no limit) |
512
|
|
|
* |
513
|
|
|
* @var int |
514
|
|
|
*/ |
515
|
|
|
protected $min_id = 0; |
516
|
|
|
|
517
|
|
|
/** |
518
|
|
|
* Max ID to match (0 means no limit) |
519
|
|
|
* |
520
|
|
|
* @var int |
521
|
|
|
*/ |
522
|
|
|
protected $max_id = 0; |
523
|
|
|
|
524
|
|
|
/** |
525
|
|
|
* Search filters |
526
|
|
|
* |
527
|
|
|
* @var array |
528
|
|
|
*/ |
529
|
|
|
protected $filters = array(); |
530
|
|
|
|
531
|
|
|
/** |
532
|
|
|
* Group-by attribute name |
533
|
|
|
* |
534
|
|
|
* @var string |
535
|
|
|
*/ |
536
|
|
|
protected $group_by = ''; |
537
|
|
|
|
538
|
|
|
/** |
539
|
|
|
* Group-by function (to pre-process group-by attribute value with) |
540
|
|
|
* |
541
|
|
|
* @var int |
542
|
|
|
*/ |
543
|
|
|
protected $group_func = SPH_GROUPBY_DAY; |
544
|
|
|
|
545
|
|
|
/** |
546
|
|
|
* Group-by sorting clause (to sort groups in result set with) |
547
|
|
|
* |
548
|
|
|
* @var string |
549
|
|
|
*/ |
550
|
|
|
protected $group_sort = '@group desc'; |
551
|
|
|
|
552
|
|
|
/** |
553
|
|
|
* Group-by count-distinct attribute |
554
|
|
|
* |
555
|
|
|
* @var string |
556
|
|
|
*/ |
557
|
|
|
protected $group_distinct = ''; |
558
|
|
|
|
559
|
|
|
/** |
560
|
|
|
* Max matches to retrieve |
561
|
|
|
* |
562
|
|
|
* @var int |
563
|
|
|
*/ |
564
|
|
|
protected $max_matches = 1000; |
565
|
|
|
|
566
|
|
|
/** |
567
|
|
|
* Cutoff to stop searching at |
568
|
|
|
* |
569
|
|
|
* @var int |
570
|
|
|
*/ |
571
|
|
|
protected $cutoff = 0; |
572
|
|
|
|
573
|
|
|
/** |
574
|
|
|
* Distributed retries count |
575
|
|
|
* |
576
|
|
|
* @var int |
577
|
|
|
*/ |
578
|
|
|
protected $retry_count = 0; |
579
|
|
|
|
580
|
|
|
/** |
581
|
|
|
* Distributed retries delay |
582
|
|
|
* |
583
|
|
|
* @var int |
584
|
|
|
*/ |
585
|
|
|
protected $retry_delay = 0; |
586
|
|
|
|
587
|
|
|
/** |
588
|
|
|
* Geographical anchor point |
589
|
|
|
* |
590
|
|
|
* @var array |
591
|
|
|
*/ |
592
|
|
|
protected $anchor = array(); |
593
|
|
|
|
594
|
|
|
/** |
595
|
|
|
* Per-index weights |
596
|
|
|
* |
597
|
|
|
* @var array |
598
|
|
|
*/ |
599
|
|
|
protected $index_weights = array(); |
600
|
|
|
|
601
|
|
|
/** |
602
|
|
|
* Ranking mode |
603
|
|
|
* |
604
|
|
|
* @var int |
605
|
|
|
*/ |
606
|
|
|
protected $ranker = SPH_RANK_PROXIMITY_BM25; |
607
|
|
|
|
608
|
|
|
/** |
609
|
|
|
* Ranking mode expression (for SPH_RANK_EXPR) |
610
|
|
|
* |
611
|
|
|
* @var string |
612
|
|
|
*/ |
613
|
|
|
protected $rank_expr = ''; |
614
|
|
|
|
615
|
|
|
/** |
616
|
|
|
* Max query time, milliseconds (0 means no limit) |
617
|
|
|
* |
618
|
|
|
* @var int |
619
|
|
|
*/ |
620
|
|
|
protected $max_query_time = 0; |
621
|
|
|
|
622
|
|
|
/** |
623
|
|
|
* Per-field-name weights |
624
|
|
|
* |
625
|
|
|
* @var array |
626
|
|
|
*/ |
627
|
|
|
protected $field_weights = array(); |
628
|
|
|
|
629
|
|
|
/** |
630
|
|
|
* Per-query attribute values overrides |
631
|
|
|
* |
632
|
|
|
* @var array |
633
|
|
|
*/ |
634
|
|
|
protected $overrides = array(); |
635
|
|
|
|
636
|
|
|
/** |
637
|
|
|
* Select-list (attributes or expressions, with optional aliases) |
638
|
|
|
* |
639
|
|
|
* @var string |
640
|
|
|
*/ |
641
|
|
|
protected $select = '*'; |
642
|
|
|
|
643
|
|
|
/** |
644
|
|
|
* Per-query various flags |
645
|
|
|
* |
646
|
|
|
* @var int |
647
|
|
|
*/ |
648
|
|
|
protected $query_flags = 0; |
649
|
|
|
|
650
|
|
|
/** |
651
|
|
|
* Per-query max_predicted_time |
652
|
|
|
* |
653
|
|
|
* @var int |
654
|
|
|
*/ |
655
|
|
|
protected $predicted_time = 0; |
656
|
|
|
|
657
|
|
|
/** |
658
|
|
|
* Outer match sort by |
659
|
|
|
* |
660
|
|
|
* @var string |
661
|
|
|
*/ |
662
|
|
|
protected $outer_order_by = ''; |
663
|
|
|
|
664
|
|
|
/** |
665
|
|
|
* Outer offset |
666
|
|
|
* |
667
|
|
|
* @var int |
668
|
|
|
*/ |
669
|
|
|
protected $outer_offset = 0; |
670
|
|
|
|
671
|
|
|
/** |
672
|
|
|
* Outer limit |
673
|
|
|
* |
674
|
|
|
* @var int |
675
|
|
|
*/ |
676
|
|
|
protected $outer_limit = 0; |
677
|
|
|
|
678
|
|
|
/** |
679
|
|
|
* @var bool |
680
|
|
|
*/ |
681
|
|
|
protected $has_outer = false; |
682
|
|
|
|
683
|
|
|
/** |
684
|
|
|
* Last error message |
685
|
|
|
* |
686
|
|
|
* @var string |
687
|
|
|
*/ |
688
|
|
|
protected $error = ''; |
689
|
|
|
|
690
|
|
|
/** |
691
|
|
|
* Last warning message |
692
|
|
|
* |
693
|
|
|
* @var string |
694
|
|
|
*/ |
695
|
|
|
protected $warning = ''; |
696
|
|
|
|
697
|
|
|
/** |
698
|
|
|
* Connection error vs remote error flag |
699
|
|
|
* |
700
|
|
|
* @var bool |
701
|
|
|
*/ |
702
|
|
|
protected $conn_error = false; |
703
|
|
|
|
704
|
|
|
/** |
705
|
|
|
* Requests array for multi-query |
706
|
|
|
* |
707
|
|
|
* @var array |
708
|
|
|
*/ |
709
|
|
|
protected $reqs = array(); |
710
|
|
|
|
711
|
|
|
/** |
712
|
|
|
* Stored mbstring encoding |
713
|
|
|
* |
714
|
|
|
* @var string |
715
|
|
|
*/ |
716
|
|
|
protected $mbenc = ''; |
717
|
|
|
|
718
|
|
|
/** |
719
|
|
|
* Whether $result['matches'] should be a hash or an array |
720
|
|
|
* |
721
|
|
|
* @var bool |
722
|
|
|
*/ |
723
|
|
|
protected $array_result = false; |
724
|
|
|
|
725
|
|
|
/** |
726
|
|
|
* Connect timeout |
727
|
|
|
* |
728
|
|
|
* @var int |
729
|
|
|
*/ |
730
|
|
|
protected $timeout = 0; |
731
|
|
|
|
732
|
|
|
/** |
733
|
|
|
* @var bool |
734
|
|
|
*/ |
735
|
|
|
protected $path = false; |
736
|
|
|
|
737
|
|
|
/** |
738
|
|
|
* @var resource|bool |
739
|
|
|
*/ |
740
|
|
|
protected $socket = false; |
741
|
|
|
|
742
|
|
|
///////////////////////////////////////////////////////////////////////////// |
743
|
|
|
// common stuff |
744
|
|
|
///////////////////////////////////////////////////////////////////////////// |
745
|
|
|
|
746
|
4 |
|
public function __construct() |
747
|
|
|
{ |
748
|
|
|
// default idf=tfidf_normalized |
749
|
4 |
|
$this->query_flags = sphSetBit(0, 6, true); |
750
|
4 |
|
} |
751
|
|
|
|
752
|
|
|
public function __destruct() |
753
|
|
|
{ |
754
|
|
|
if ($this->socket !== false) { |
755
|
|
|
fclose($this->socket); |
756
|
|
|
} |
757
|
|
|
} |
758
|
|
|
|
759
|
|
|
/** |
760
|
|
|
* @return string |
761
|
|
|
*/ |
762
|
1 |
|
public function getLastError() |
763
|
|
|
{ |
764
|
1 |
|
return $this->error; |
765
|
|
|
} |
766
|
|
|
|
767
|
|
|
/** |
768
|
|
|
* @return string |
769
|
|
|
*/ |
770
|
1 |
|
public function getLastWarning() |
771
|
|
|
{ |
772
|
1 |
|
return $this->warning; |
773
|
|
|
} |
774
|
|
|
|
775
|
|
|
/** |
776
|
|
|
* Get last error flag (to tell network connection errors from searchd errors or broken responses) |
777
|
|
|
* |
778
|
|
|
* @return bool |
779
|
|
|
*/ |
780
|
1 |
|
public function isConnectError() |
781
|
|
|
{ |
782
|
1 |
|
return $this->conn_error; |
783
|
|
|
} |
784
|
|
|
|
785
|
|
|
/** |
786
|
|
|
* Set searchd host name and port |
787
|
|
|
* |
788
|
|
|
* @param string $host |
789
|
|
|
* @param int $port |
790
|
|
|
*/ |
791
|
|
|
public function setServer($host, $port = 0) |
792
|
|
|
{ |
793
|
|
|
assert(is_string($host)); |
794
|
|
|
if ($host[0] == '/') { |
795
|
|
|
$this->path = 'unix://' . $host; |
|
|
|
|
796
|
|
|
return; |
797
|
|
|
} |
798
|
|
|
if (substr($host, 0, 7) == 'unix://') { |
799
|
|
|
$this->path = $host; |
|
|
|
|
800
|
|
|
return; |
801
|
|
|
} |
802
|
|
|
|
803
|
|
|
$this->host = $host; |
804
|
|
|
$port = intval($port); |
805
|
|
|
assert(0 <= $port && $port < 65536); |
806
|
|
|
$this->port = $port == 0 ? 9312 : $port; |
807
|
|
|
$this->path = ''; |
|
|
|
|
808
|
|
|
} |
809
|
|
|
|
810
|
|
|
/** |
811
|
|
|
* Set server connection timeout (0 to remove) |
812
|
|
|
* |
813
|
|
|
* @param int $timeout |
814
|
|
|
*/ |
815
|
|
|
public function setConnectTimeout($timeout) |
816
|
|
|
{ |
817
|
|
|
assert(is_numeric($timeout)); |
818
|
|
|
$this->timeout = $timeout; |
|
|
|
|
819
|
|
|
} |
820
|
|
|
|
821
|
|
|
/** |
822
|
|
|
* @param resource $handle |
823
|
|
|
* @param string $data |
824
|
|
|
* @param int $length |
825
|
|
|
* |
826
|
|
|
* @return bool |
827
|
|
|
*/ |
828
|
|
|
protected function send($handle, $data, $length) |
829
|
|
|
{ |
830
|
|
|
if (feof($handle) || fwrite($handle, $data, $length) !== $length) { |
831
|
|
|
$this->error = 'connection unexpectedly closed (timed out?)'; |
832
|
|
|
$this->conn_error = true; |
833
|
|
|
return false; |
834
|
|
|
} |
835
|
|
|
return true; |
836
|
|
|
} |
837
|
|
|
|
838
|
|
|
///////////////////////////////////////////////////////////////////////////// |
839
|
|
|
|
840
|
|
|
/** |
841
|
|
|
* Enter mbstring workaround mode |
842
|
|
|
*/ |
843
|
|
|
protected function mbPush() |
844
|
|
|
{ |
845
|
|
|
$this->mbenc = ''; |
846
|
|
|
if (ini_get('mbstring.func_overload') & 2) { |
847
|
|
|
$this->mbenc = mb_internal_encoding(); |
848
|
|
|
mb_internal_encoding('latin1'); |
849
|
|
|
} |
850
|
|
|
} |
851
|
|
|
|
852
|
|
|
/** |
853
|
|
|
* Leave mbstring workaround mode |
854
|
|
|
*/ |
855
|
|
|
protected function mbPop() |
856
|
|
|
{ |
857
|
|
|
if ($this->mbenc) { |
858
|
|
|
mb_internal_encoding($this->mbenc); |
859
|
|
|
} |
860
|
|
|
} |
861
|
|
|
|
862
|
|
|
/** |
863
|
|
|
* Connect to searchd server |
864
|
|
|
* |
865
|
|
|
* @return bool|resource |
866
|
|
|
*/ |
867
|
|
|
protected function connect() |
868
|
|
|
{ |
869
|
|
|
if ($this->socket !== false) { |
870
|
|
|
// we are in persistent connection mode, so we have a socket |
871
|
|
|
// however, need to check whether it's still alive |
872
|
|
|
if (!@feof($this->socket)) { |
873
|
|
|
return $this->socket; |
874
|
|
|
} |
875
|
|
|
|
876
|
|
|
// force reopen |
877
|
|
|
$this->socket = false; |
878
|
|
|
} |
879
|
|
|
|
880
|
|
|
$errno = 0; |
881
|
|
|
$errstr = ''; |
882
|
|
|
$this->conn_error = false; |
883
|
|
|
|
884
|
|
|
if ($this->path) { |
885
|
|
|
$host = $this->path; |
886
|
|
|
$port = 0; |
887
|
|
|
} else { |
888
|
|
|
$host = $this->host; |
889
|
|
|
$port = $this->port; |
890
|
|
|
} |
891
|
|
|
|
892
|
|
|
if ($this->timeout <= 0) { |
893
|
|
|
$fp = @fsockopen($host, $port, $errno, $errstr); |
894
|
|
|
} else { |
895
|
|
|
$fp = @fsockopen($host, $port, $errno, $errstr, $this->timeout); |
896
|
|
|
} |
897
|
|
|
|
898
|
|
|
if (!$fp) { |
899
|
|
|
if ($this->path) { |
900
|
|
|
$location = $this->path; |
901
|
|
|
} else { |
902
|
|
|
$location = "{$this->host}:{$this->port}"; |
903
|
|
|
} |
904
|
|
|
|
905
|
|
|
$errstr = trim($errstr); |
906
|
|
|
$this->error = "connection to $location failed (errno=$errno, msg=$errstr)"; |
907
|
|
|
$this->conn_error = true; |
908
|
|
|
return false; |
909
|
|
|
} |
910
|
|
|
|
911
|
|
|
// send my version |
912
|
|
|
// this is a subtle part. we must do it before (!) reading back from searchd. |
913
|
|
|
// because otherwise under some conditions (reported on FreeBSD for instance) |
914
|
|
|
// TCP stack could throttle write-write-read pattern because of Nagle. |
915
|
|
|
if (!$this->send($fp, pack('N', 1), 4)) { |
916
|
|
|
fclose($fp); |
917
|
|
|
$this->error = 'failed to send client protocol version'; |
918
|
|
|
return false; |
919
|
|
|
} |
920
|
|
|
|
921
|
|
|
// check version |
922
|
|
|
list(, $v) = unpack('N*', fread($fp, 4)); |
923
|
|
|
$v = (int)$v; |
924
|
|
|
if ($v < 1) { |
925
|
|
|
fclose($fp); |
926
|
|
|
$this->error = "expected searchd protocol version 1+, got version '$v'"; |
927
|
|
|
return false; |
928
|
|
|
} |
929
|
|
|
|
930
|
|
|
return $fp; |
931
|
|
|
} |
932
|
|
|
|
933
|
|
|
/** |
934
|
|
|
* Get and check response packet from searchd server |
935
|
|
|
* |
936
|
|
|
* @param resource $fp |
937
|
|
|
* @param int $client_ver |
938
|
|
|
* |
939
|
|
|
* @return bool|string |
940
|
|
|
*/ |
941
|
|
|
protected function getResponse($fp, $client_ver) |
942
|
|
|
{ |
943
|
|
|
$response = ''; |
944
|
|
|
$len = 0; |
945
|
|
|
|
946
|
|
|
$header = fread($fp, 8); |
947
|
|
|
if (strlen($header) == 8) { |
948
|
|
|
list($status, $ver, $len) = array_values(unpack('n2a/Nb', $header)); |
949
|
|
|
$left = $len; |
950
|
|
|
while ($left > 0 && !feof($fp)) { |
951
|
|
|
$chunk = fread($fp, min(8192, $left)); |
952
|
|
|
if ($chunk) { |
953
|
|
|
$response .= $chunk; |
954
|
|
|
$left -= strlen($chunk); |
955
|
|
|
} |
956
|
|
|
} |
957
|
|
|
} |
958
|
|
|
|
959
|
|
|
if ($this->socket === false) { |
960
|
|
|
fclose($fp); |
961
|
|
|
} |
962
|
|
|
|
963
|
|
|
// check response |
964
|
|
|
$read = strlen($response); |
965
|
|
|
if (!$response || $read != $len) { |
966
|
|
|
$this->error = $len |
967
|
|
|
? "failed to read searchd response (status=$status, ver=$ver, len=$len, read=$read)" |
|
|
|
|
968
|
|
|
: 'received zero-sized searchd response'; |
969
|
|
|
return false; |
970
|
|
|
} |
971
|
|
|
|
972
|
|
|
// check status |
973
|
|
|
if ($status == SEARCHD_WARNING) { |
974
|
|
|
list(, $wlen) = unpack('N*', substr($response, 0, 4)); |
975
|
|
|
$this->warning = substr($response, 4, $wlen); |
976
|
|
|
return substr($response, 4 + $wlen); |
977
|
|
|
} |
978
|
|
|
if ($status == SEARCHD_ERROR) { |
979
|
|
|
$this->error = 'searchd error: ' . substr($response, 4); |
980
|
|
|
return false; |
981
|
|
|
} |
982
|
|
|
if ($status == SEARCHD_RETRY) { |
983
|
|
|
$this->error = 'temporary searchd error: ' . substr($response, 4); |
984
|
|
|
return false; |
985
|
|
|
} |
986
|
|
|
if ($status != SEARCHD_OK) { |
987
|
|
|
$this->error = "unknown status code '$status'"; |
988
|
|
|
return false; |
989
|
|
|
} |
990
|
|
|
|
991
|
|
|
// check version |
992
|
|
|
if ($ver < $client_ver) { |
993
|
|
|
$this->warning = sprintf( |
994
|
|
|
'searchd command v.%d.%d older than client\'s v.%d.%d, some options might not work', |
995
|
|
|
$ver >> 8, |
996
|
|
|
$ver & 0xff, |
997
|
|
|
$client_ver >> 8, |
998
|
|
|
$client_ver & 0xff |
999
|
|
|
); |
1000
|
|
|
} |
1001
|
|
|
|
1002
|
|
|
return $response; |
1003
|
|
|
} |
1004
|
|
|
|
1005
|
|
|
///////////////////////////////////////////////////////////////////////////// |
1006
|
|
|
// searching |
1007
|
|
|
///////////////////////////////////////////////////////////////////////////// |
1008
|
|
|
|
1009
|
|
|
/** |
1010
|
|
|
* Set offset and count into result set, and optionally set max-matches and cutoff limits |
1011
|
|
|
* |
1012
|
|
|
* @param int $offset |
1013
|
|
|
* @param int $limit |
1014
|
|
|
* @param int $max |
1015
|
|
|
* @param int $cutoff |
1016
|
|
|
*/ |
1017
|
|
|
public function setLimits($offset, $limit, $max = 0, $cutoff = 0) |
1018
|
|
|
{ |
1019
|
|
|
assert(is_int($offset)); |
1020
|
|
|
assert(is_int($limit)); |
1021
|
|
|
assert($offset >= 0); |
1022
|
|
|
assert($limit > 0); |
1023
|
|
|
assert($max >= 0); |
1024
|
|
|
$this->offset = $offset; |
1025
|
|
|
$this->limit = $limit; |
1026
|
|
|
if ($max > 0) { |
1027
|
|
|
$this->max_matches = $max; |
1028
|
|
|
} |
1029
|
|
|
if ($cutoff > 0) { |
1030
|
|
|
$this->cutoff = $cutoff; |
1031
|
|
|
} |
1032
|
|
|
} |
1033
|
|
|
|
1034
|
|
|
/** |
1035
|
|
|
* Set maximum query time, in milliseconds, per-index, 0 means 'do not limit' |
1036
|
|
|
* |
1037
|
|
|
* @param int $max |
1038
|
|
|
*/ |
1039
|
|
|
public function setMaxQueryTime($max) |
1040
|
|
|
{ |
1041
|
|
|
assert(is_int($max)); |
1042
|
|
|
assert($max >= 0); |
1043
|
|
|
$this->max_query_time = $max; |
1044
|
|
|
} |
1045
|
|
|
|
1046
|
|
|
/** |
1047
|
|
|
* Set matching mode |
1048
|
|
|
* |
1049
|
|
|
* @param int $mode |
1050
|
|
|
*/ |
1051
|
|
|
public function setMatchMode($mode) |
1052
|
|
|
{ |
1053
|
|
|
trigger_error( |
1054
|
|
|
'DEPRECATED: Do not call this method or, even better, use SphinxQL instead of an API', |
1055
|
|
|
E_USER_DEPRECATED |
1056
|
|
|
); |
1057
|
|
|
assert(in_array($mode, array( |
1058
|
|
|
SPH_MATCH_ALL, |
1059
|
|
|
SPH_MATCH_ANY, |
1060
|
|
|
SPH_MATCH_PHRASE, |
1061
|
|
|
SPH_MATCH_BOOLEAN, |
1062
|
|
|
SPH_MATCH_EXTENDED, |
1063
|
|
|
SPH_MATCH_FULLSCAN, |
1064
|
|
|
SPH_MATCH_EXTENDED2 |
1065
|
|
|
))); |
1066
|
|
|
$this->mode = $mode; |
1067
|
|
|
} |
1068
|
|
|
|
1069
|
|
|
/** |
1070
|
|
|
* Set ranking mode |
1071
|
|
|
* |
1072
|
|
|
* @param int $ranker |
1073
|
|
|
* @param string $rank_expr |
1074
|
|
|
*/ |
1075
|
|
|
public function setRankingMode($ranker, $rank_expr='') |
1076
|
|
|
{ |
1077
|
|
|
assert($ranker === 0 || $ranker >= 1 && $ranker < SPH_RANK_TOTAL); |
1078
|
|
|
assert(is_string($rank_expr)); |
1079
|
|
|
$this->ranker = $ranker; |
1080
|
|
|
$this->rank_expr = $rank_expr; |
1081
|
|
|
} |
1082
|
|
|
|
1083
|
|
|
/** |
1084
|
|
|
* Set matches sorting mode |
1085
|
|
|
* |
1086
|
|
|
* @param int $mode |
1087
|
|
|
* @param string $sort_by |
1088
|
|
|
*/ |
1089
|
|
|
public function setSortMode($mode, $sort_by = '') |
1090
|
|
|
{ |
1091
|
|
|
assert(in_array($mode, array( |
1092
|
|
|
SPH_SORT_RELEVANCE, |
1093
|
|
|
SPH_SORT_ATTR_DESC, |
1094
|
|
|
SPH_SORT_ATTR_ASC, |
1095
|
|
|
SPH_SORT_TIME_SEGMENTS, |
1096
|
|
|
SPH_SORT_EXTENDED, |
1097
|
|
|
SPH_SORT_EXPR |
1098
|
|
|
))); |
1099
|
|
|
assert(is_string($sort_by)); |
1100
|
|
|
assert($mode == SPH_SORT_RELEVANCE || strlen($sort_by) > 0); |
1101
|
|
|
|
1102
|
|
|
$this->sort = $mode; |
1103
|
|
|
$this->sort_by = $sort_by; |
1104
|
|
|
} |
1105
|
|
|
|
1106
|
|
|
/** |
1107
|
|
|
* Bind per-field weights by order |
1108
|
|
|
* |
1109
|
|
|
* @deprecated use setFieldWeights() instead |
1110
|
|
|
*/ |
1111
|
1 |
|
public function setWeights() |
1112
|
|
|
{ |
1113
|
1 |
|
throw new \RuntimeException('This method is now deprecated; please use setFieldWeights instead'); |
1114
|
|
|
} |
1115
|
|
|
|
1116
|
|
|
/** |
1117
|
|
|
* Bind per-field weights by name |
1118
|
|
|
* |
1119
|
|
|
* @param array $weights |
1120
|
|
|
*/ |
1121
|
|
|
public function setFieldWeights(array $weights) |
1122
|
|
|
{ |
1123
|
|
|
foreach ($weights as $name => $weight) { |
1124
|
|
|
assert(is_string($name)); |
1125
|
|
|
assert(is_int($weight)); |
1126
|
|
|
} |
1127
|
|
|
$this->field_weights = $weights; |
1128
|
|
|
} |
1129
|
|
|
|
1130
|
|
|
/** |
1131
|
|
|
* Bind per-index weights by name |
1132
|
|
|
* |
1133
|
|
|
* @param array $weights |
1134
|
|
|
*/ |
1135
|
|
|
public function setIndexWeights(array $weights) |
1136
|
|
|
{ |
1137
|
|
|
foreach ($weights as $index => $weight) { |
1138
|
|
|
assert(is_string($index)); |
1139
|
|
|
assert(is_int($weight)); |
1140
|
|
|
} |
1141
|
|
|
$this->index_weights = $weights; |
1142
|
|
|
} |
1143
|
|
|
|
1144
|
|
|
/** |
1145
|
|
|
* Set IDs range to match. Only match records if document ID is beetwen $min and $max (inclusive) |
1146
|
|
|
* |
1147
|
|
|
* @param int $min |
1148
|
|
|
* @param int $max |
1149
|
|
|
*/ |
1150
|
|
|
public function setIDRange($min, $max) |
1151
|
|
|
{ |
1152
|
|
|
assert(is_numeric($min)); |
1153
|
|
|
assert(is_numeric($max)); |
1154
|
|
|
assert($min <= $max); |
1155
|
|
|
|
1156
|
|
|
$this->min_id = $min; |
|
|
|
|
1157
|
|
|
$this->max_id = $max; |
|
|
|
|
1158
|
|
|
} |
1159
|
|
|
|
1160
|
|
|
/** |
1161
|
|
|
* Set values set filter. Only match records where $attribute value is in given set |
1162
|
|
|
* |
1163
|
|
|
* @param string $attribute |
1164
|
|
|
* @param array $values |
1165
|
|
|
* @param bool $exclude |
1166
|
|
|
*/ |
1167
|
|
View Code Duplication |
public function setFilter($attribute, array $values, $exclude = false) |
|
|
|
|
1168
|
|
|
{ |
1169
|
|
|
assert(is_string($attribute)); |
1170
|
|
|
assert(count($values)); |
1171
|
|
|
|
1172
|
|
|
foreach ($values as $value) { |
1173
|
|
|
assert(is_numeric($value)); |
1174
|
|
|
} |
1175
|
|
|
|
1176
|
|
|
$this->filters[] = array( |
1177
|
|
|
'type' => SPH_FILTER_VALUES, |
1178
|
|
|
'attr' => $attribute, |
1179
|
|
|
'exclude' => $exclude, |
1180
|
|
|
'values' => $values |
1181
|
|
|
); |
1182
|
|
|
} |
1183
|
|
|
|
1184
|
|
|
/** |
1185
|
|
|
* Set string filter |
1186
|
|
|
* Only match records where $attribute value is equal |
1187
|
|
|
* |
1188
|
|
|
* @param string $attribute |
1189
|
|
|
* @param string $value |
1190
|
|
|
* @param bool $exclude |
1191
|
|
|
*/ |
1192
|
|
|
public function setFilterString($attribute, $value, $exclude = false) |
1193
|
|
|
{ |
1194
|
|
|
assert(is_string($attribute)); |
1195
|
|
|
assert(is_string($value)); |
1196
|
|
|
$this->filters[] = array( |
1197
|
|
|
'type' => SPH_FILTER_STRING, |
1198
|
|
|
'attr' => $attribute, |
1199
|
|
|
'exclude' => $exclude, |
1200
|
|
|
'value' => $value |
1201
|
|
|
); |
1202
|
|
|
} |
1203
|
|
|
|
1204
|
|
|
/** |
1205
|
|
|
* Set range filter |
1206
|
|
|
* Only match records if $attribute value is beetwen $min and $max (inclusive) |
1207
|
|
|
* |
1208
|
|
|
* @param string $attribute |
1209
|
|
|
* @param int $min |
1210
|
|
|
* @param int $max |
1211
|
|
|
* @param bool $exclude |
1212
|
|
|
*/ |
1213
|
|
View Code Duplication |
public function setFilterRange($attribute, $min, $max, $exclude = false) |
|
|
|
|
1214
|
|
|
{ |
1215
|
|
|
assert(is_string($attribute)); |
1216
|
|
|
assert(is_numeric($min)); |
1217
|
|
|
assert(is_numeric($max)); |
1218
|
|
|
assert($min <= $max); |
1219
|
|
|
|
1220
|
|
|
$this->filters[] = array( |
1221
|
|
|
'type' => SPH_FILTER_RANGE, |
1222
|
|
|
'attr' => $attribute, |
1223
|
|
|
'exclude' => $exclude, |
1224
|
|
|
'min' => $min, |
1225
|
|
|
'max' => $max |
1226
|
|
|
); |
1227
|
|
|
} |
1228
|
|
|
|
1229
|
|
|
/** |
1230
|
|
|
* Set float range filter |
1231
|
|
|
* Only match records if $attribute value is beetwen $min and $max (inclusive) |
1232
|
|
|
* |
1233
|
|
|
* @param string $attribute |
1234
|
|
|
* @param int $min |
1235
|
|
|
* @param int $max |
1236
|
|
|
* @param bool $exclude |
1237
|
|
|
*/ |
1238
|
|
View Code Duplication |
public function setFilterFloatRange($attribute, $min, $max, $exclude = false) |
|
|
|
|
1239
|
|
|
{ |
1240
|
|
|
assert(is_string($attribute)); |
1241
|
|
|
assert(is_float($min)); |
1242
|
|
|
assert(is_float($max)); |
1243
|
|
|
assert($min <= $max); |
1244
|
|
|
|
1245
|
|
|
$this->filters[] = array( |
1246
|
|
|
'type' => SPH_FILTER_FLOATRANGE, |
1247
|
|
|
'attr' => $attribute, |
1248
|
|
|
'exclude' => $exclude, |
1249
|
|
|
'min' => $min, |
1250
|
|
|
'max' => $max |
1251
|
|
|
); |
1252
|
|
|
} |
1253
|
|
|
|
1254
|
|
|
/** |
1255
|
|
|
* Setup anchor point for geosphere distance calculations |
1256
|
|
|
* Required to use @geodist in filters and sorting |
1257
|
|
|
* Latitude and longitude must be in radians |
1258
|
|
|
* |
1259
|
|
|
* @param string $attr_lat |
1260
|
|
|
* @param string $attr_long |
1261
|
|
|
* @param float $lat |
1262
|
|
|
* @param float $long |
1263
|
|
|
*/ |
1264
|
|
|
public function setGeoAnchor($attr_lat, $attr_long, $lat, $long) |
1265
|
|
|
{ |
1266
|
|
|
assert(is_string($attr_lat)); |
1267
|
|
|
assert(is_string($attr_long)); |
1268
|
|
|
assert(is_float($lat)); |
1269
|
|
|
assert(is_float($long)); |
1270
|
|
|
|
1271
|
|
|
$this->anchor = array( |
1272
|
|
|
'attrlat' => $attr_lat, |
1273
|
|
|
'attrlong' => $attr_long, |
1274
|
|
|
'lat' => $lat, |
1275
|
|
|
'long' => $long |
1276
|
|
|
); |
1277
|
|
|
} |
1278
|
|
|
|
1279
|
|
|
/** |
1280
|
|
|
* Set grouping attribute and function |
1281
|
|
|
* |
1282
|
|
|
* @param string $attribute |
1283
|
|
|
* @param string $func |
1284
|
|
|
* @param string $group_sort |
1285
|
|
|
*/ |
1286
|
|
|
public function setGroupBy($attribute, $func, $group_sort = '@group desc') |
1287
|
|
|
{ |
1288
|
|
|
assert(is_string($attribute)); |
1289
|
|
|
assert(is_string($group_sort)); |
1290
|
|
|
assert(in_array($func, array( |
1291
|
|
|
SPH_GROUPBY_DAY, |
1292
|
|
|
SPH_GROUPBY_WEEK, |
1293
|
|
|
SPH_GROUPBY_MONTH, |
1294
|
|
|
SPH_GROUPBY_YEAR, |
1295
|
|
|
SPH_GROUPBY_ATTR, |
1296
|
|
|
SPH_GROUPBY_ATTRPAIR |
1297
|
|
|
))); |
1298
|
|
|
|
1299
|
|
|
$this->group_by = $attribute; |
1300
|
|
|
$this->group_func = $func; |
|
|
|
|
1301
|
|
|
$this->group_sort = $group_sort; |
1302
|
|
|
} |
1303
|
|
|
|
1304
|
|
|
/** |
1305
|
|
|
* Set count-distinct attribute for group-by queries |
1306
|
|
|
* |
1307
|
|
|
* @param string $attribute |
1308
|
|
|
*/ |
1309
|
|
|
public function setGroupDistinct($attribute) |
1310
|
|
|
{ |
1311
|
|
|
assert(is_string($attribute)); |
1312
|
|
|
$this->group_distinct = $attribute; |
1313
|
|
|
} |
1314
|
|
|
|
1315
|
|
|
/** |
1316
|
|
|
* Set distributed retries count and delay |
1317
|
|
|
* |
1318
|
|
|
* @param int $count |
1319
|
|
|
* @param int $delay |
1320
|
|
|
*/ |
1321
|
|
|
public function setRetries($count, $delay = 0) |
1322
|
|
|
{ |
1323
|
|
|
assert(is_int($count) && $count >= 0); |
1324
|
|
|
assert(is_int($delay) && $delay >= 0); |
1325
|
|
|
$this->retry_count = $count; |
1326
|
|
|
$this->retry_delay = $delay; |
1327
|
|
|
} |
1328
|
|
|
|
1329
|
|
|
/** |
1330
|
|
|
* Set result set format (hash or array; hash by default) |
1331
|
|
|
* PHP specific; needed for group-by-MVA result sets that may contain duplicate IDs |
1332
|
|
|
* |
1333
|
|
|
* @param bool $array_result |
1334
|
|
|
*/ |
1335
|
|
|
public function setArrayResult($array_result) |
1336
|
|
|
{ |
1337
|
|
|
assert(is_bool($array_result)); |
1338
|
|
|
$this->array_result = $array_result; |
1339
|
|
|
} |
1340
|
|
|
|
1341
|
|
|
/** |
1342
|
|
|
* Set attribute values override |
1343
|
|
|
* There can be only one override per attribute |
1344
|
|
|
* $values must be a hash that maps document IDs to attribute values |
1345
|
|
|
* |
1346
|
|
|
* @deprecated Do not call this method. Use SphinxQL REMAP() function instead. |
1347
|
|
|
* |
1348
|
|
|
* @param string $attr_name |
1349
|
|
|
* @param string $attr_type |
1350
|
|
|
* @param array $values |
1351
|
|
|
*/ |
1352
|
|
|
public function setOverride($attr_name, $attr_type, array $values) |
1353
|
|
|
{ |
1354
|
|
|
trigger_error( |
1355
|
|
|
'DEPRECATED: Do not call this method. Use SphinxQL REMAP() function instead.', |
1356
|
|
|
E_USER_DEPRECATED |
1357
|
|
|
); |
1358
|
|
|
assert(is_string($attr_name)); |
1359
|
|
|
assert(in_array($attr_type, array( |
1360
|
|
|
SPH_ATTR_INTEGER, |
1361
|
|
|
SPH_ATTR_TIMESTAMP, |
1362
|
|
|
SPH_ATTR_BOOL, |
1363
|
|
|
SPH_ATTR_FLOAT, |
1364
|
|
|
SPH_ATTR_BIGINT |
1365
|
|
|
))); |
1366
|
|
|
|
1367
|
|
|
$this->overrides[$attr_name] = array( |
1368
|
|
|
'attr' => $attr_name, |
1369
|
|
|
'type' => $attr_type, |
1370
|
|
|
'values' => $values |
1371
|
|
|
); |
1372
|
|
|
} |
1373
|
|
|
|
1374
|
|
|
/** |
1375
|
|
|
* Set select-list (attributes or expressions), SQL-like syntax |
1376
|
|
|
* |
1377
|
|
|
* @param string $select |
1378
|
|
|
*/ |
1379
|
|
|
public function setSelect($select) |
1380
|
|
|
{ |
1381
|
|
|
assert(is_string($select)); |
1382
|
|
|
$this->select = $select; |
1383
|
|
|
} |
1384
|
|
|
|
1385
|
|
|
/** |
1386
|
|
|
* @param string $flag_name |
1387
|
|
|
* @param string|int $flag_value |
1388
|
|
|
*/ |
1389
|
|
|
public function setQueryFlag($flag_name, $flag_value) |
1390
|
|
|
{ |
1391
|
|
|
$known_names = array( |
1392
|
|
|
'reverse_scan', |
1393
|
|
|
'sort_method', |
1394
|
|
|
'max_predicted_time', |
1395
|
|
|
'boolean_simplify', |
1396
|
|
|
'idf', |
1397
|
|
|
'global_idf', |
1398
|
|
|
'low_priority' |
1399
|
|
|
); |
1400
|
|
|
$flags = array ( |
1401
|
|
|
'reverse_scan' => array(0, 1), |
1402
|
|
|
'sort_method' => array('pq', 'kbuffer'), |
1403
|
|
|
'max_predicted_time' => array(0), |
1404
|
|
|
'boolean_simplify' => array(true, false), |
1405
|
|
|
'idf' => array ('normalized', 'plain', 'tfidf_normalized', 'tfidf_unnormalized'), |
1406
|
|
|
'global_idf' => array(true, false), |
1407
|
|
|
'low_priority' => array(true, false) |
1408
|
|
|
); |
1409
|
|
|
|
1410
|
|
|
assert(isset($flag_name, $known_names)); |
1411
|
|
|
assert( |
1412
|
|
|
in_array($flag_value, $flags[$flag_name], true) || |
1413
|
|
|
($flag_name == 'max_predicted_time' && is_int($flag_value) && $flag_value >= 0) |
1414
|
|
|
); |
1415
|
|
|
|
1416
|
|
|
switch ($flag_name) { |
1417
|
|
|
case 'reverse_scan': |
1418
|
|
|
$this->query_flags = sphSetBit($this->query_flags, 0, $flag_value == 1); |
1419
|
|
|
break; |
1420
|
|
|
case 'sort_method': |
1421
|
|
|
$this->query_flags = sphSetBit($this->query_flags, 1, $flag_value == 'kbuffer'); |
1422
|
|
|
break; |
1423
|
|
|
case 'max_predicted_time': |
1424
|
|
|
$this->query_flags = sphSetBit($this->query_flags, 2, $flag_value > 0); |
1425
|
|
|
$this->predicted_time = (int)$flag_value; |
1426
|
|
|
break; |
1427
|
|
|
case 'boolean_simplify': |
1428
|
|
|
$this->query_flags = sphSetBit($this->query_flags, 3, $flag_value); |
|
|
|
|
1429
|
|
|
break; |
1430
|
|
|
case 'idf': |
1431
|
|
|
if ($flag_value == 'normalized' || $flag_value == 'plain') { |
1432
|
|
|
$this->query_flags = sphSetBit($this->query_flags, 4, $flag_value == 'plain'); |
1433
|
|
|
} |
1434
|
|
|
if ($flag_value == 'tfidf_normalized' || $flag_value == 'tfidf_unnormalized') { |
1435
|
|
|
$this->query_flags = sphSetBit($this->query_flags, 6, $flag_value == 'tfidf_normalized'); |
1436
|
|
|
} |
1437
|
|
|
break; |
1438
|
|
|
case 'global_idf': |
1439
|
|
|
$this->query_flags = sphSetBit($this->query_flags, 5, $flag_value); |
|
|
|
|
1440
|
|
|
break; |
1441
|
|
|
case 'low_priority': |
1442
|
|
|
$this->query_flags = sphSetBit($this->query_flags, 8, $flag_value); |
|
|
|
|
1443
|
|
|
break; |
1444
|
|
|
} |
1445
|
|
|
} |
1446
|
|
|
|
1447
|
|
|
/** |
1448
|
|
|
* Set outer order by parameters |
1449
|
|
|
* |
1450
|
|
|
* @param string $order_by |
1451
|
|
|
* @param int $offset |
1452
|
|
|
* @param int $limit |
1453
|
|
|
*/ |
1454
|
|
|
public function setOuterSelect($order_by, $offset, $limit) |
1455
|
|
|
{ |
1456
|
|
|
assert(is_string($order_by)); |
1457
|
|
|
assert(is_int($offset)); |
1458
|
|
|
assert(is_int($limit)); |
1459
|
|
|
assert($offset >= 0); |
1460
|
|
|
assert($limit > 0); |
1461
|
|
|
|
1462
|
|
|
$this->outer_order_by = $order_by; |
1463
|
|
|
$this->outer_offset = $offset; |
1464
|
|
|
$this->outer_limit = $limit; |
1465
|
|
|
$this->has_outer = true; |
1466
|
|
|
} |
1467
|
|
|
|
1468
|
|
|
|
1469
|
|
|
////////////////////////////////////////////////////////////////////////////// |
1470
|
|
|
|
1471
|
|
|
/** |
1472
|
|
|
* Clear all filters (for multi-queries) |
1473
|
|
|
*/ |
1474
|
|
|
public function resetFilters() |
1475
|
|
|
{ |
1476
|
|
|
$this->filters = array(); |
1477
|
|
|
$this->anchor = array(); |
1478
|
|
|
} |
1479
|
|
|
|
1480
|
|
|
/** |
1481
|
|
|
* Clear groupby settings (for multi-queries) |
1482
|
|
|
*/ |
1483
|
|
|
public function resetGroupBy() |
1484
|
|
|
{ |
1485
|
|
|
$this->group_by = ''; |
1486
|
|
|
$this->group_func = SPH_GROUPBY_DAY; |
1487
|
|
|
$this->group_sort = '@group desc'; |
1488
|
|
|
$this->group_distinct = ''; |
1489
|
|
|
} |
1490
|
|
|
|
1491
|
|
|
/** |
1492
|
|
|
* Clear all attribute value overrides (for multi-queries) |
1493
|
|
|
*/ |
1494
|
|
|
public function resetOverrides() |
1495
|
|
|
{ |
1496
|
|
|
$this->overrides = array(); |
1497
|
|
|
} |
1498
|
|
|
|
1499
|
|
|
public function resetQueryFlag() |
1500
|
|
|
{ |
1501
|
|
|
$this->query_flags = sphSetBit(0, 6, true); // default idf=tfidf_normalized |
1502
|
|
|
$this->predicted_time = 0; |
1503
|
|
|
} |
1504
|
|
|
|
1505
|
|
|
public function resetOuterSelect() |
1506
|
|
|
{ |
1507
|
|
|
$this->outer_order_by = ''; |
1508
|
|
|
$this->outer_offset = 0; |
1509
|
|
|
$this->outer_limit = 0; |
1510
|
|
|
$this->has_outer = false; |
1511
|
|
|
} |
1512
|
|
|
|
1513
|
|
|
////////////////////////////////////////////////////////////////////////////// |
1514
|
|
|
|
1515
|
|
|
/** |
1516
|
|
|
* Connect to searchd server, run given search query through given indexes, and return the search results |
1517
|
|
|
* |
1518
|
|
|
* @param string $query |
1519
|
|
|
* @param string $index |
1520
|
|
|
* @param string $comment |
1521
|
|
|
* |
1522
|
|
|
* @return bool |
1523
|
|
|
*/ |
1524
|
|
|
public function query($query, $index = '*', $comment = '') |
1525
|
|
|
{ |
1526
|
|
|
assert(empty($this->reqs)); |
1527
|
|
|
|
1528
|
|
|
$this->addQuery($query, $index, $comment); |
1529
|
|
|
$results = $this->runQueries(); |
1530
|
|
|
$this->reqs = array(); // just in case it failed too early |
1531
|
|
|
|
1532
|
|
|
if (!is_array($results)) { |
1533
|
|
|
return false; // probably network error; error message should be already filled |
1534
|
|
|
} |
1535
|
|
|
|
1536
|
|
|
$this->error = $results[0]['error']; |
1537
|
|
|
$this->warning = $results[0]['warning']; |
1538
|
|
|
|
1539
|
|
|
if ($results[0]['status'] == SEARCHD_ERROR) { |
1540
|
|
|
return false; |
1541
|
|
|
} else { |
1542
|
|
|
return $results[0]; |
1543
|
|
|
} |
1544
|
|
|
} |
1545
|
|
|
|
1546
|
|
|
/** |
1547
|
|
|
* Helper to pack floats in network byte order |
1548
|
|
|
* |
1549
|
|
|
* @param float $float |
1550
|
|
|
* |
1551
|
|
|
* @return string |
1552
|
|
|
*/ |
1553
|
|
|
protected function packFloat($float) |
1554
|
|
|
{ |
1555
|
|
|
$t1 = pack('f', $float); // machine order |
1556
|
|
|
list(, $t2) = unpack('L*', $t1); // int in machine order |
1557
|
|
|
return pack('N', $t2); |
1558
|
|
|
} |
1559
|
|
|
|
1560
|
|
|
/** |
1561
|
|
|
* Add query to multi-query batch |
1562
|
|
|
* Returns index into results array from RunQueries() call |
1563
|
|
|
* |
1564
|
|
|
* @param string $query |
1565
|
|
|
* @param string $index |
1566
|
|
|
* @param string $comment |
1567
|
|
|
* |
1568
|
|
|
* @return int |
1569
|
|
|
*/ |
1570
|
|
|
public function addQuery($query, $index = '*', $comment = '') |
1571
|
|
|
{ |
1572
|
|
|
// mbstring workaround |
1573
|
|
|
$this->mbPush(); |
1574
|
|
|
|
1575
|
|
|
// build request |
1576
|
|
|
$req = pack('NNNNN', $this->query_flags, $this->offset, $this->limit, $this->mode, $this->ranker); |
1577
|
|
|
if ($this->ranker == SPH_RANK_EXPR) { |
1578
|
|
|
$req .= pack('N', strlen($this->rank_expr)) . $this->rank_expr; |
1579
|
|
|
} |
1580
|
|
|
$req .= pack('N', $this->sort); // (deprecated) sort mode |
1581
|
|
|
$req .= pack('N', strlen($this->sort_by)) . $this->sort_by; |
1582
|
|
|
$req .= pack('N', strlen($query)) . $query; // query itself |
1583
|
|
|
$req .= pack('N', count($this->weights)); // weights |
1584
|
|
|
foreach ($this->weights as $weight) { |
1585
|
|
|
$req .= pack('N', (int)$weight); |
1586
|
|
|
} |
1587
|
|
|
$req .= pack('N', strlen($index)) . $index; // indexes |
1588
|
|
|
$req .= pack('N', 1); // id64 range marker |
1589
|
|
|
$req .= sphPackU64($this->min_id) . sphPackU64($this->max_id); // id64 range |
1590
|
|
|
|
1591
|
|
|
// filters |
1592
|
|
|
$req .= pack('N', count($this->filters)); |
1593
|
|
|
foreach ($this->filters as $filter) { |
1594
|
|
|
$req .= pack('N', strlen($filter['attr'])) . $filter['attr']; |
1595
|
|
|
$req .= pack('N', $filter['type']); |
1596
|
|
|
switch ($filter['type']) { |
1597
|
|
|
case SPH_FILTER_VALUES: |
1598
|
|
|
$req .= pack('N', count($filter['values'])); |
1599
|
|
|
foreach ($filter['values'] as $value) { |
1600
|
|
|
$req .= sphPackI64($value); |
1601
|
|
|
} |
1602
|
|
|
break; |
1603
|
|
|
case SPH_FILTER_RANGE: |
1604
|
|
|
$req .= sphPackI64($filter['min']) . sphPackI64($filter['max']); |
1605
|
|
|
break; |
1606
|
|
|
case SPH_FILTER_FLOATRANGE: |
1607
|
|
|
$req .= $this->packFloat($filter['min']) . $this->packFloat($filter['max']); |
1608
|
|
|
break; |
1609
|
|
|
case SPH_FILTER_STRING: |
1610
|
|
|
$req .= pack('N', strlen($filter['value'])) . $filter['value']; |
1611
|
|
|
break; |
1612
|
|
|
default: |
1613
|
|
|
assert(0 && 'internal error: unhandled filter type'); |
1614
|
|
|
} |
1615
|
|
|
$req .= pack('N', $filter['exclude']); |
1616
|
|
|
} |
1617
|
|
|
|
1618
|
|
|
// group-by clause, max-matches count, group-sort clause, cutoff count |
1619
|
|
|
$req .= pack('NN', $this->group_func, strlen($this->group_by)) . $this->group_by; |
1620
|
|
|
$req .= pack('N', $this->max_matches); |
1621
|
|
|
$req .= pack('N', strlen($this->group_sort)) . $this->group_sort; |
1622
|
|
|
$req .= pack('NNN', $this->cutoff, $this->retry_count, $this->retry_delay); |
1623
|
|
|
$req .= pack('N', strlen($this->group_distinct)) . $this->group_distinct; |
1624
|
|
|
|
1625
|
|
|
// anchor point |
1626
|
|
|
if (empty($this->anchor)) { |
1627
|
|
|
$req .= pack('N', 0); |
1628
|
|
|
} else { |
1629
|
|
|
$a =& $this->anchor; |
1630
|
|
|
$req .= pack('N', 1); |
1631
|
|
|
$req .= pack('N', strlen($a['attrlat'])) . $a['attrlat']; |
1632
|
|
|
$req .= pack('N', strlen($a['attrlong'])) . $a['attrlong']; |
1633
|
|
|
$req .= $this->packFloat($a['lat']) . $this->packFloat($a['long']); |
1634
|
|
|
} |
1635
|
|
|
|
1636
|
|
|
// per-index weights |
1637
|
|
|
$req .= pack('N', count($this->index_weights)); |
1638
|
|
|
foreach ($this->index_weights as $idx => $weight) { |
1639
|
|
|
$req .= pack('N', strlen($idx)) . $idx . pack('N', $weight); |
1640
|
|
|
} |
1641
|
|
|
|
1642
|
|
|
// max query time |
1643
|
|
|
$req .= pack('N', $this->max_query_time); |
1644
|
|
|
|
1645
|
|
|
// per-field weights |
1646
|
|
|
$req .= pack('N', count($this->field_weights)); |
1647
|
|
|
foreach ($this->field_weights as $field => $weight) { |
1648
|
|
|
$req .= pack('N', strlen($field)) . $field . pack('N', $weight); |
1649
|
|
|
} |
1650
|
|
|
|
1651
|
|
|
// comment |
1652
|
|
|
$req .= pack('N', strlen($comment)) . $comment; |
1653
|
|
|
|
1654
|
|
|
// attribute overrides |
1655
|
|
|
$req .= pack('N', count($this->overrides)); |
1656
|
|
|
foreach ($this->overrides as $key => $entry) { |
1657
|
|
|
$req .= pack('N', strlen($entry['attr'])) . $entry['attr']; |
1658
|
|
|
$req .= pack('NN', $entry['type'], count($entry['values'])); |
1659
|
|
|
foreach ($entry['values'] as $id => $val) { |
1660
|
|
|
assert(is_numeric($id)); |
1661
|
|
|
assert(is_numeric($val)); |
1662
|
|
|
|
1663
|
|
|
$req .= sphPackU64($id); |
1664
|
|
|
switch ($entry['type']) { |
1665
|
|
|
case SPH_ATTR_FLOAT: |
1666
|
|
|
$req .= $this->packFloat($val); |
1667
|
|
|
break; |
1668
|
|
|
case SPH_ATTR_BIGINT: |
1669
|
|
|
$req .= sphPackI64($val); |
1670
|
|
|
break; |
1671
|
|
|
default: |
1672
|
|
|
$req .= pack('N', $val); |
1673
|
|
|
break; |
1674
|
|
|
} |
1675
|
|
|
} |
1676
|
|
|
} |
1677
|
|
|
|
1678
|
|
|
// select-list |
1679
|
|
|
$req .= pack('N', strlen($this->select)) . $this->select; |
1680
|
|
|
|
1681
|
|
|
// max_predicted_time |
1682
|
|
|
if ($this->predicted_time > 0) { |
1683
|
|
|
$req .= pack('N', (int)$this->predicted_time); |
1684
|
|
|
} |
1685
|
|
|
|
1686
|
|
|
$req .= pack('N', strlen($this->outer_order_by)) . $this->outer_order_by; |
1687
|
|
|
$req .= pack('NN', $this->outer_offset, $this->outer_limit); |
1688
|
|
|
if ($this->has_outer) { |
1689
|
|
|
$req .= pack('N', 1); |
1690
|
|
|
} else { |
1691
|
|
|
$req .= pack('N', 0); |
1692
|
|
|
} |
1693
|
|
|
|
1694
|
|
|
// mbstring workaround |
1695
|
|
|
$this->mbPop(); |
1696
|
|
|
|
1697
|
|
|
// store request to requests array |
1698
|
|
|
$this->reqs[] = $req; |
1699
|
|
|
return count($this->reqs) - 1; |
1700
|
|
|
} |
1701
|
|
|
|
1702
|
|
|
/** |
1703
|
|
|
* Connect to searchd, run queries batch, and return an array of result sets |
1704
|
|
|
* |
1705
|
|
|
* @return array|bool |
1706
|
|
|
*/ |
1707
|
|
|
public function runQueries() |
1708
|
|
|
{ |
1709
|
|
|
if (empty($this->reqs)) { |
1710
|
|
|
$this->error = 'no queries defined, issue AddQuery() first'; |
1711
|
|
|
return false; |
1712
|
|
|
} |
1713
|
|
|
|
1714
|
|
|
// mbstring workaround |
1715
|
|
|
$this->mbPush(); |
1716
|
|
|
|
1717
|
|
|
if (!($fp = $this->connect())) { |
1718
|
|
|
$this->mbPop(); |
1719
|
|
|
return false; |
1720
|
|
|
} |
1721
|
|
|
|
1722
|
|
|
// send query, get response |
1723
|
|
|
$nreqs = count($this->reqs); |
1724
|
|
|
$req = join('', $this->reqs); |
1725
|
|
|
$len = 8 + strlen($req); |
1726
|
|
|
$req = pack('nnNNN', SEARCHD_COMMAND_SEARCH, VER_COMMAND_SEARCH, $len, 0, $nreqs) . $req; // add header |
1727
|
|
|
|
1728
|
|
View Code Duplication |
if (!$this->send($fp, $req, $len + 8) || !($response = $this->getResponse($fp, VER_COMMAND_SEARCH))) { |
|
|
|
|
1729
|
|
|
$this->mbPop(); |
1730
|
|
|
return false; |
1731
|
|
|
} |
1732
|
|
|
|
1733
|
|
|
// query sent ok; we can reset reqs now |
1734
|
|
|
$this->reqs = array(); |
1735
|
|
|
|
1736
|
|
|
// parse and return response |
1737
|
|
|
return $this->parseSearchResponse($response, $nreqs); |
1738
|
|
|
} |
1739
|
|
|
|
1740
|
|
|
/** |
1741
|
|
|
* Parse and return search query (or queries) response |
1742
|
|
|
* |
1743
|
|
|
* @param string $response |
1744
|
|
|
* @param int $nreqs |
1745
|
|
|
* |
1746
|
|
|
* @return array |
1747
|
|
|
*/ |
1748
|
|
|
protected function parseSearchResponse($response, $nreqs) |
1749
|
|
|
{ |
1750
|
|
|
$p = 0; // current position |
1751
|
|
|
$max = strlen($response); // max position for checks, to protect against broken responses |
1752
|
|
|
|
1753
|
|
|
$results = array(); |
1754
|
|
|
for ($ires = 0; $ires < $nreqs && $p < $max; $ires++) { |
1755
|
|
|
$results[] = array(); |
1756
|
|
|
$result =& $results[$ires]; |
1757
|
|
|
|
1758
|
|
|
$result['error'] = ''; |
1759
|
|
|
$result['warning'] = ''; |
1760
|
|
|
|
1761
|
|
|
// extract status |
1762
|
|
|
list(, $status) = unpack('N*', substr($response, $p, 4)); |
1763
|
|
|
$p += 4; |
1764
|
|
|
$result['status'] = $status; |
1765
|
|
|
if ($status != SEARCHD_OK) { |
1766
|
|
|
list(, $len) = unpack('N*', substr($response, $p, 4)); |
1767
|
|
|
$p += 4; |
1768
|
|
|
$message = substr($response, $p, $len); |
1769
|
|
|
$p += $len; |
1770
|
|
|
|
1771
|
|
|
if ($status == SEARCHD_WARNING) { |
1772
|
|
|
$result['warning'] = $message; |
1773
|
|
|
} else { |
1774
|
|
|
$result['error'] = $message; |
1775
|
|
|
continue; |
1776
|
|
|
} |
1777
|
|
|
} |
1778
|
|
|
|
1779
|
|
|
// read schema |
1780
|
|
|
$fields = array(); |
1781
|
|
|
$attrs = array(); |
1782
|
|
|
|
1783
|
|
|
list(, $nfields) = unpack('N*', substr($response, $p, 4)); |
1784
|
|
|
$p += 4; |
1785
|
|
|
while ($nfields --> 0 && $p < $max) { |
1786
|
|
|
list(, $len) = unpack('N*', substr($response, $p, 4)); |
1787
|
|
|
$p += 4; |
1788
|
|
|
$fields[] = substr($response, $p, $len); |
1789
|
|
|
$p += $len; |
1790
|
|
|
} |
1791
|
|
|
$result['fields'] = $fields; |
1792
|
|
|
|
1793
|
|
|
list(, $nattrs) = unpack('N*', substr($response, $p, 4)); |
1794
|
|
|
$p += 4; |
1795
|
|
|
while ($nattrs --> 0 && $p < $max) { |
1796
|
|
|
list(, $len) = unpack('N*', substr($response, $p, 4)); |
1797
|
|
|
$p += 4; |
1798
|
|
|
$attr = substr($response, $p, $len); |
1799
|
|
|
$p += $len; |
1800
|
|
|
list(, $type) = unpack('N*', substr($response, $p, 4)); |
1801
|
|
|
$p += 4; |
1802
|
|
|
$attrs[$attr] = $type; |
1803
|
|
|
} |
1804
|
|
|
$result['attrs'] = $attrs; |
1805
|
|
|
|
1806
|
|
|
// read match count |
1807
|
|
|
list(, $count) = unpack('N*', substr($response, $p, 4)); |
1808
|
|
|
$p += 4; |
1809
|
|
|
list(, $id64) = unpack('N*', substr($response, $p, 4)); |
1810
|
|
|
$p += 4; |
1811
|
|
|
|
1812
|
|
|
// read matches |
1813
|
|
|
$idx = -1; |
1814
|
|
|
while ($count --> 0 && $p < $max) { |
1815
|
|
|
// index into result array |
1816
|
|
|
$idx++; |
1817
|
|
|
|
1818
|
|
|
// parse document id and weight |
1819
|
|
|
if ($id64) { |
1820
|
|
|
$doc = sphUnpackU64(substr($response, $p, 8)); |
1821
|
|
|
$p += 8; |
1822
|
|
|
list(,$weight) = unpack('N*', substr($response, $p, 4)); |
1823
|
|
|
$p += 4; |
1824
|
|
|
} else { |
1825
|
|
|
list($doc, $weight) = array_values(unpack('N*N*', substr($response, $p, 8))); |
1826
|
|
|
$p += 8; |
1827
|
|
|
$doc = sphFixUint($doc); |
1828
|
|
|
} |
1829
|
|
|
$weight = sprintf('%u', $weight); |
1830
|
|
|
|
1831
|
|
|
// create match entry |
1832
|
|
View Code Duplication |
if ($this->array_result) { |
|
|
|
|
1833
|
|
|
$result['matches'][$idx] = array('id' => $doc, 'weight' => $weight); |
1834
|
|
|
} else { |
1835
|
|
|
$result['matches'][$doc]['weight'] = $weight; |
1836
|
|
|
} |
1837
|
|
|
|
1838
|
|
|
// parse and create attributes |
1839
|
|
|
$attrvals = array(); |
1840
|
|
|
foreach ($attrs as $attr => $type) { |
1841
|
|
|
// handle 64bit ints |
1842
|
|
|
if ($type == SPH_ATTR_BIGINT) { |
1843
|
|
|
$attrvals[$attr] = sphUnpackI64(substr($response, $p, 8)); |
1844
|
|
|
$p += 8; |
1845
|
|
|
continue; |
1846
|
|
|
} |
1847
|
|
|
|
1848
|
|
|
// handle floats |
1849
|
|
|
if ($type == SPH_ATTR_FLOAT) { |
1850
|
|
|
list(, $uval) = unpack('N*', substr($response, $p, 4)); |
1851
|
|
|
$p += 4; |
1852
|
|
|
list(, $fval) = unpack('f*', pack('L', $uval)); |
1853
|
|
|
$attrvals[$attr] = $fval; |
1854
|
|
|
continue; |
1855
|
|
|
} |
1856
|
|
|
|
1857
|
|
|
// handle everything else as unsigned ints |
1858
|
|
|
list(, $val) = unpack('N*', substr($response, $p, 4)); |
1859
|
|
|
$p += 4; |
1860
|
|
|
if ($type == SPH_ATTR_MULTI) { |
1861
|
|
|
$attrvals[$attr] = array(); |
1862
|
|
|
$nvalues = $val; |
1863
|
|
View Code Duplication |
while ($nvalues --> 0 && $p < $max) { |
|
|
|
|
1864
|
|
|
list(, $val) = unpack('N*', substr($response, $p, 4)); |
1865
|
|
|
$p += 4; |
1866
|
|
|
$attrvals[$attr][] = sphFixUint($val); |
1867
|
|
|
} |
1868
|
|
|
} elseif ($type == SPH_ATTR_MULTI64) { |
1869
|
|
|
$attrvals[$attr] = array(); |
1870
|
|
|
$nvalues = $val; |
1871
|
|
View Code Duplication |
while ($nvalues > 0 && $p < $max) { |
|
|
|
|
1872
|
|
|
$attrvals[$attr][] = sphUnpackI64(substr($response, $p, 8)); |
1873
|
|
|
$p += 8; |
1874
|
|
|
$nvalues -= 2; |
1875
|
|
|
} |
1876
|
|
|
} elseif ($type == SPH_ATTR_STRING) { |
1877
|
|
|
$attrvals[$attr] = substr($response, $p, $val); |
1878
|
|
|
$p += $val; |
1879
|
|
|
} elseif ($type == SPH_ATTR_FACTORS) { |
1880
|
|
|
$attrvals[$attr] = substr($response, $p, $val - 4); |
1881
|
|
|
$p += $val-4; |
1882
|
|
|
} else { |
1883
|
|
|
$attrvals[$attr] = sphFixUint($val); |
1884
|
|
|
} |
1885
|
|
|
} |
1886
|
|
|
|
1887
|
|
View Code Duplication |
if ($this->array_result) { |
|
|
|
|
1888
|
|
|
$result['matches'][$idx]['attrs'] = $attrvals; |
1889
|
|
|
} else { |
1890
|
|
|
$result['matches'][$doc]['attrs'] = $attrvals; |
1891
|
|
|
} |
1892
|
|
|
} |
1893
|
|
|
|
1894
|
|
|
list($total, $total_found, $msecs, $words) = array_values(unpack('N*N*N*N*', substr($response, $p, 16))); |
1895
|
|
|
$result['total'] = sprintf('%u', $total); |
1896
|
|
|
$result['total_found'] = sprintf('%u', $total_found); |
1897
|
|
|
$result['time'] = sprintf('%.3f', $msecs / 1000); |
1898
|
|
|
$p += 16; |
1899
|
|
|
|
1900
|
|
|
while ($words --> 0 && $p < $max) { |
1901
|
|
|
list(, $len) = unpack('N*', substr($response, $p, 4)); |
1902
|
|
|
$p += 4; |
1903
|
|
|
$word = substr($response, $p, $len); |
1904
|
|
|
$p += $len; |
1905
|
|
|
list($docs, $hits) = array_values(unpack('N*N*', substr($response, $p, 8))); |
1906
|
|
|
$p += 8; |
1907
|
|
|
$result['words'][$word] = array ( |
1908
|
|
|
'docs' => sprintf('%u', $docs), |
1909
|
|
|
'hits' => sprintf('%u', $hits) |
1910
|
|
|
); |
1911
|
|
|
} |
1912
|
|
|
} |
1913
|
|
|
|
1914
|
|
|
$this->mbPop(); |
1915
|
|
|
return $results; |
1916
|
|
|
} |
1917
|
|
|
|
1918
|
|
|
///////////////////////////////////////////////////////////////////////////// |
1919
|
|
|
// excerpts generation |
1920
|
|
|
///////////////////////////////////////////////////////////////////////////// |
1921
|
|
|
|
1922
|
|
|
/** |
1923
|
|
|
* Connect to searchd server, and generate exceprts (snippets) of given documents for given query. |
1924
|
|
|
* Returns false on failure, an array of snippets on success |
1925
|
|
|
* |
1926
|
|
|
* @param array $docs |
1927
|
|
|
* @param string $index |
1928
|
|
|
* @param string $words |
1929
|
|
|
* @param array $opts |
1930
|
|
|
* |
1931
|
|
|
* @return array|bool |
1932
|
|
|
*/ |
1933
|
|
|
public function buildExcerpts(array $docs, $index, $words, array $opts = array()) |
1934
|
|
|
{ |
1935
|
|
|
assert(is_string($index)); |
1936
|
|
|
assert(is_string($words)); |
1937
|
|
|
|
1938
|
|
|
$this->mbPush(); |
1939
|
|
|
|
1940
|
|
|
if (!($fp = $this->connect())) { |
1941
|
|
|
$this->mbPop(); |
1942
|
|
|
return false; |
1943
|
|
|
} |
1944
|
|
|
|
1945
|
|
|
///////////////// |
1946
|
|
|
// fixup options |
1947
|
|
|
///////////////// |
1948
|
|
|
|
1949
|
|
|
$opts = array_merge(array( |
1950
|
|
|
'before_match' => '<b>', |
1951
|
|
|
'after_match' => '</b>', |
1952
|
|
|
'chunk_separator' => ' ... ', |
1953
|
|
|
'limit' => 256, |
1954
|
|
|
'limit_passages' => 0, |
1955
|
|
|
'limit_words' => 0, |
1956
|
|
|
'around' => 5, |
1957
|
|
|
'exact_phrase' => false, |
1958
|
|
|
'single_passage' => false, |
1959
|
|
|
'use_boundaries' => false, |
1960
|
|
|
'weight_order' => false, |
1961
|
|
|
'query_mode' => false, |
1962
|
|
|
'force_all_words' => false, |
1963
|
|
|
'start_passage_id' => 1, |
1964
|
|
|
'load_files' => false, |
1965
|
|
|
'html_strip_mode' => 'index', |
1966
|
|
|
'allow_empty' => false, |
1967
|
|
|
'passage_boundary' => 'none', |
1968
|
|
|
'emit_zones' => false, |
1969
|
|
|
'load_files_scattered' => false |
1970
|
|
|
), $opts); |
1971
|
|
|
|
1972
|
|
|
///////////////// |
1973
|
|
|
// build request |
1974
|
|
|
///////////////// |
1975
|
|
|
|
1976
|
|
|
// v.1.2 req |
1977
|
|
|
$flags = 1; // remove spaces |
1978
|
|
|
if ($opts['exact_phrase']) { |
1979
|
|
|
$flags |= 2; |
1980
|
|
|
} |
1981
|
|
|
if ($opts['single_passage']) { |
1982
|
|
|
$flags |= 4; |
1983
|
|
|
} |
1984
|
|
|
if ($opts['use_boundaries']) { |
1985
|
|
|
$flags |= 8; |
1986
|
|
|
} |
1987
|
|
|
if ($opts['weight_order']) { |
1988
|
|
|
$flags |= 16; |
1989
|
|
|
} |
1990
|
|
|
if ($opts['query_mode']) { |
1991
|
|
|
$flags |= 32; |
1992
|
|
|
} |
1993
|
|
|
if ($opts['force_all_words']) { |
1994
|
|
|
$flags |= 64; |
1995
|
|
|
} |
1996
|
|
|
if ($opts['load_files']) { |
1997
|
|
|
$flags |= 128; |
1998
|
|
|
} |
1999
|
|
|
if ($opts['allow_empty']) { |
2000
|
|
|
$flags |= 256; |
2001
|
|
|
} |
2002
|
|
|
if ($opts['emit_zones']) { |
2003
|
|
|
$flags |= 512; |
2004
|
|
|
} |
2005
|
|
|
if ($opts['load_files_scattered']) { |
2006
|
|
|
$flags |= 1024; |
2007
|
|
|
} |
2008
|
|
|
$req = pack('NN', 0, $flags); // mode=0, flags=$flags |
2009
|
|
|
$req .= pack('N', strlen($index)) . $index; // req index |
2010
|
|
|
$req .= pack('N', strlen($words)) . $words; // req words |
2011
|
|
|
|
2012
|
|
|
// options |
2013
|
|
|
$req .= pack('N', strlen($opts['before_match'])) . $opts['before_match']; |
2014
|
|
|
$req .= pack('N', strlen($opts['after_match'])) . $opts['after_match']; |
2015
|
|
|
$req .= pack('N', strlen($opts['chunk_separator'])) . $opts['chunk_separator']; |
2016
|
|
|
$req .= pack('NN', (int)$opts['limit'], (int)$opts['around']); |
2017
|
|
|
$req .= pack('NNN', (int)$opts['limit_passages'], (int)$opts['limit_words'], (int)$opts['start_passage_id']); // v.1.2 |
2018
|
|
|
$req .= pack('N', strlen($opts['html_strip_mode'])) . $opts['html_strip_mode']; |
2019
|
|
|
$req .= pack('N', strlen($opts['passage_boundary'])) . $opts['passage_boundary']; |
2020
|
|
|
|
2021
|
|
|
// documents |
2022
|
|
|
$req .= pack('N', count($docs)); |
2023
|
|
|
foreach ($docs as $doc) { |
2024
|
|
|
assert(is_string($doc)); |
2025
|
|
|
$req .= pack('N', strlen($doc)) . $doc; |
2026
|
|
|
} |
2027
|
|
|
|
2028
|
|
|
//////////////////////////// |
2029
|
|
|
// send query, get response |
2030
|
|
|
//////////////////////////// |
2031
|
|
|
|
2032
|
|
|
$len = strlen($req); |
2033
|
|
|
$req = pack('nnN', SEARCHD_COMMAND_EXCERPT, VER_COMMAND_EXCERPT, $len) . $req; // add header |
2034
|
|
View Code Duplication |
if (!$this->send($fp, $req, $len + 8) || !($response = $this->getResponse($fp, VER_COMMAND_EXCERPT))) { |
|
|
|
|
2035
|
|
|
$this->mbPop(); |
2036
|
|
|
return false; |
2037
|
|
|
} |
2038
|
|
|
|
2039
|
|
|
////////////////// |
2040
|
|
|
// parse response |
2041
|
|
|
////////////////// |
2042
|
|
|
|
2043
|
|
|
$pos = 0; |
2044
|
|
|
$res = array(); |
2045
|
|
|
$rlen = strlen($response); |
2046
|
|
|
$count = count($docs); |
2047
|
|
|
while ($count--) { |
2048
|
|
|
list(, $len) = unpack('N*', substr($response, $pos, 4)); |
2049
|
|
|
$pos += 4; |
2050
|
|
|
|
2051
|
|
|
if ($pos + $len > $rlen) { |
2052
|
|
|
$this->error = 'incomplete reply'; |
2053
|
|
|
$this->mbPop(); |
2054
|
|
|
return false; |
2055
|
|
|
} |
2056
|
|
|
$res[] = $len ? substr($response, $pos, $len) : ''; |
2057
|
|
|
$pos += $len; |
2058
|
|
|
} |
2059
|
|
|
|
2060
|
|
|
$this->mbPop(); |
2061
|
|
|
return $res; |
2062
|
|
|
} |
2063
|
|
|
|
2064
|
|
|
|
2065
|
|
|
///////////////////////////////////////////////////////////////////////////// |
2066
|
|
|
// keyword generation |
2067
|
|
|
///////////////////////////////////////////////////////////////////////////// |
2068
|
|
|
|
2069
|
|
|
/** |
2070
|
|
|
* Connect to searchd server, and generate keyword list for a given query returns false on failure, |
2071
|
|
|
* an array of words on success |
2072
|
|
|
* |
2073
|
|
|
* @param string $query |
2074
|
|
|
* @param string $index |
2075
|
|
|
* @param bool $hits |
2076
|
|
|
* |
2077
|
|
|
* @return array|bool |
2078
|
|
|
*/ |
2079
|
|
|
public function buildKeywords($query, $index, $hits) |
2080
|
|
|
{ |
2081
|
|
|
assert(is_string($query)); |
2082
|
|
|
assert(is_string($index)); |
2083
|
|
|
assert(is_bool($hits)); |
2084
|
|
|
|
2085
|
|
|
$this->mbPush(); |
2086
|
|
|
|
2087
|
|
|
if (!($fp = $this->connect())) { |
2088
|
|
|
$this->mbPop(); |
2089
|
|
|
return false; |
2090
|
|
|
} |
2091
|
|
|
|
2092
|
|
|
///////////////// |
2093
|
|
|
// build request |
2094
|
|
|
///////////////// |
2095
|
|
|
|
2096
|
|
|
// v.1.0 req |
2097
|
|
|
$req = pack('N', strlen($query)) . $query; // req query |
2098
|
|
|
$req .= pack('N', strlen($index)) . $index; // req index |
2099
|
|
|
$req .= pack('N', (int)$hits); |
2100
|
|
|
|
2101
|
|
|
//////////////////////////// |
2102
|
|
|
// send query, get response |
2103
|
|
|
//////////////////////////// |
2104
|
|
|
|
2105
|
|
|
$len = strlen($req); |
2106
|
|
|
$req = pack('nnN', SEARCHD_COMMAND_KEYWORDS, VER_COMMAND_KEYWORDS, $len) . $req; // add header |
2107
|
|
View Code Duplication |
if (!$this->send($fp, $req, $len + 8) || !($response = $this->getResponse($fp, VER_COMMAND_KEYWORDS))) { |
|
|
|
|
2108
|
|
|
$this->mbPop(); |
2109
|
|
|
return false; |
2110
|
|
|
} |
2111
|
|
|
|
2112
|
|
|
////////////////// |
2113
|
|
|
// parse response |
2114
|
|
|
////////////////// |
2115
|
|
|
|
2116
|
|
|
$pos = 0; |
2117
|
|
|
$res = array(); |
2118
|
|
|
$rlen = strlen($response); |
2119
|
|
|
list(, $nwords) = unpack('N*', substr($response, $pos, 4)); |
2120
|
|
|
$pos += 4; |
2121
|
|
|
for ($i = 0; $i < $nwords; $i++) { |
2122
|
|
|
list(, $len) = unpack('N*', substr($response, $pos, 4)); |
2123
|
|
|
$pos += 4; |
2124
|
|
|
$tokenized = $len ? substr($response, $pos, $len) : ''; |
2125
|
|
|
$pos += $len; |
2126
|
|
|
|
2127
|
|
|
list(, $len) = unpack('N*', substr($response, $pos, 4)); |
2128
|
|
|
$pos += 4; |
2129
|
|
|
$normalized = $len ? substr($response, $pos, $len) : ''; |
2130
|
|
|
$pos += $len; |
2131
|
|
|
|
2132
|
|
|
$res[] = array( |
2133
|
|
|
'tokenized' => $tokenized, |
2134
|
|
|
'normalized' => $normalized |
2135
|
|
|
); |
2136
|
|
|
|
2137
|
|
|
if ($hits) { |
2138
|
|
|
list($ndocs, $nhits) = array_values(unpack('N*N*', substr($response, $pos, 8))); |
2139
|
|
|
$pos += 8; |
2140
|
|
|
$res[$i]['docs'] = $ndocs; |
2141
|
|
|
$res[$i]['hits'] = $nhits; |
2142
|
|
|
} |
2143
|
|
|
|
2144
|
|
|
if ($pos > $rlen) { |
2145
|
|
|
$this->error = 'incomplete reply'; |
2146
|
|
|
$this->mbPop(); |
2147
|
|
|
return false; |
2148
|
|
|
} |
2149
|
|
|
} |
2150
|
|
|
|
2151
|
|
|
$this->mbPop(); |
2152
|
|
|
return $res; |
2153
|
|
|
} |
2154
|
|
|
|
2155
|
|
|
/** |
2156
|
|
|
* @param string $string |
2157
|
|
|
* |
2158
|
|
|
* @return string |
2159
|
|
|
*/ |
2160
|
|
|
public function escapeString($string) |
2161
|
|
|
{ |
2162
|
|
|
$from = array('\\', '(',')','|','-','!','@','~','"','&', '/', '^', '$', '=', '<'); |
2163
|
|
|
$to = array('\\\\', '\(','\)','\|','\-','\!','\@','\~','\"', '\&', '\/', '\^', '\$', '\=', '\<'); |
2164
|
|
|
|
2165
|
|
|
return str_replace($from, $to, $string); |
2166
|
|
|
} |
2167
|
|
|
|
2168
|
|
|
///////////////////////////////////////////////////////////////////////////// |
2169
|
|
|
// attribute updates |
2170
|
|
|
///////////////////////////////////////////////////////////////////////////// |
2171
|
|
|
|
2172
|
|
|
/** |
2173
|
|
|
* Batch update given attributes in given rows in given indexes |
2174
|
|
|
* Returns amount of updated documents (0 or more) on success, or -1 on failure |
2175
|
|
|
* |
2176
|
|
|
* @param string $index |
2177
|
|
|
* @param array $attrs |
2178
|
|
|
* @param array $values |
2179
|
|
|
* @param bool $mva |
2180
|
|
|
* @param bool $ignore_non_existent |
2181
|
|
|
* |
2182
|
|
|
* @return int |
2183
|
|
|
*/ |
2184
|
|
|
public function updateAttributes($index, array $attrs, array $values, $mva = false, $ignore_non_existent = false) |
2185
|
|
|
{ |
2186
|
|
|
// verify everything |
2187
|
|
|
assert(is_string($index)); |
2188
|
|
|
assert(is_bool($mva)); |
2189
|
|
|
assert(is_bool($ignore_non_existent)); |
2190
|
|
|
|
2191
|
|
|
foreach ($attrs as $attr) { |
2192
|
|
|
assert(is_string($attr)); |
2193
|
|
|
} |
2194
|
|
|
|
2195
|
|
|
foreach ($values as $id => $entry) { |
2196
|
|
|
assert(is_numeric($id)); |
2197
|
|
|
assert(is_array($entry)); |
2198
|
|
|
assert(count($entry) == count($attrs)); |
2199
|
|
|
foreach ($entry as $v) { |
2200
|
|
|
if ($mva) { |
2201
|
|
|
assert(is_array($v)); |
2202
|
|
|
foreach ($v as $vv) { |
2203
|
|
|
assert(is_int($vv)); |
2204
|
|
|
} |
2205
|
|
|
} else { |
2206
|
|
|
assert(is_int($v)); |
2207
|
|
|
} |
2208
|
|
|
} |
2209
|
|
|
} |
2210
|
|
|
|
2211
|
|
|
// build request |
2212
|
|
|
$this->mbPush(); |
2213
|
|
|
$req = pack('N', strlen($index)) . $index; |
2214
|
|
|
|
2215
|
|
|
$req .= pack('N', count($attrs)); |
2216
|
|
|
$req .= pack('N', $ignore_non_existent ? 1 : 0); |
2217
|
|
|
foreach ($attrs as $attr) { |
2218
|
|
|
$req .= pack('N', strlen($attr)) . $attr; |
2219
|
|
|
$req .= pack('N', $mva ? 1 : 0); |
2220
|
|
|
} |
2221
|
|
|
|
2222
|
|
|
$req .= pack('N', count($values)); |
2223
|
|
|
foreach ($values as $id => $entry) { |
2224
|
|
|
$req .= sphPackU64($id); |
2225
|
|
|
foreach ($entry as $v) { |
2226
|
|
|
$req .= pack('N', $mva ? count($v) : $v); |
2227
|
|
|
if ($mva) { |
2228
|
|
|
foreach ($v as $vv) { |
2229
|
|
|
$req .= pack('N', $vv); |
2230
|
|
|
} |
2231
|
|
|
} |
2232
|
|
|
} |
2233
|
|
|
} |
2234
|
|
|
|
2235
|
|
|
// connect, send query, get response |
2236
|
|
|
if (!($fp = $this->connect())) { |
2237
|
|
|
$this->mbPop(); |
2238
|
|
|
return -1; |
2239
|
|
|
} |
2240
|
|
|
|
2241
|
|
|
$len = strlen($req); |
2242
|
|
|
$req = pack('nnN', SEARCHD_COMMAND_UPDATE, VER_COMMAND_UPDATE, $len) . $req; // add header |
2243
|
|
|
if (!$this->send($fp, $req, $len + 8)) { |
|
|
|
|
2244
|
|
|
$this->mbPop(); |
2245
|
|
|
return -1; |
2246
|
|
|
} |
2247
|
|
|
|
2248
|
|
|
if (!($response = $this->getResponse($fp, VER_COMMAND_UPDATE))) { |
|
|
|
|
2249
|
|
|
$this->mbPop(); |
2250
|
|
|
return -1; |
2251
|
|
|
} |
2252
|
|
|
|
2253
|
|
|
// parse response |
2254
|
|
|
list(, $updated) = unpack('N*', substr($response, 0, 4)); |
2255
|
|
|
$this->mbPop(); |
2256
|
|
|
return $updated; |
2257
|
|
|
} |
2258
|
|
|
|
2259
|
|
|
///////////////////////////////////////////////////////////////////////////// |
2260
|
|
|
// persistent connections |
2261
|
|
|
///////////////////////////////////////////////////////////////////////////// |
2262
|
|
|
|
2263
|
|
|
/** |
2264
|
|
|
* @return bool |
2265
|
|
|
*/ |
2266
|
|
|
public function open() |
2267
|
|
|
{ |
2268
|
|
|
if ($this->socket !== false) { |
2269
|
|
|
$this->error = 'already connected'; |
2270
|
|
|
return false; |
2271
|
|
|
} |
2272
|
|
|
if (!($fp = $this->connect())) |
2273
|
|
|
return false; |
2274
|
|
|
|
2275
|
|
|
// command, command version = 0, body length = 4, body = 1 |
2276
|
|
|
$req = pack('nnNN', SEARCHD_COMMAND_PERSIST, 0, 4, 1); |
2277
|
|
|
if (!$this->send($fp, $req, 12)) { |
|
|
|
|
2278
|
|
|
return false; |
2279
|
|
|
} |
2280
|
|
|
|
2281
|
|
|
$this->socket = $fp; |
2282
|
|
|
return true; |
2283
|
|
|
} |
2284
|
|
|
|
2285
|
|
|
/** |
2286
|
|
|
* @return bool |
2287
|
|
|
*/ |
2288
|
|
|
public function close() |
2289
|
|
|
{ |
2290
|
|
|
if ($this->socket === false) { |
2291
|
|
|
$this->error = 'not connected'; |
2292
|
|
|
return false; |
2293
|
|
|
} |
2294
|
|
|
|
2295
|
|
|
fclose($this->socket); |
2296
|
|
|
$this->socket = false; |
2297
|
|
|
|
2298
|
|
|
return true; |
2299
|
|
|
} |
2300
|
|
|
|
2301
|
|
|
////////////////////////////////////////////////////////////////////////// |
2302
|
|
|
// status |
2303
|
|
|
////////////////////////////////////////////////////////////////////////// |
2304
|
|
|
|
2305
|
|
|
/** |
2306
|
|
|
* @param bool $session |
2307
|
|
|
* |
2308
|
|
|
* @return array|bool |
2309
|
|
|
*/ |
2310
|
|
|
public function status($session = false) |
2311
|
|
|
{ |
2312
|
|
|
assert(is_bool($session)); |
2313
|
|
|
|
2314
|
|
|
$this->mbPush(); |
2315
|
|
|
if (!($fp = $this->connect())) { |
2316
|
|
|
$this->mbPop(); |
2317
|
|
|
return false; |
2318
|
|
|
} |
2319
|
|
|
|
2320
|
|
|
$req = pack('nnNN', SEARCHD_COMMAND_STATUS, VER_COMMAND_STATUS, 4, $session ? 0 : 1); // len=4, body=1 |
2321
|
|
View Code Duplication |
if (!$this->send($fp, $req, 12) || !($response = $this->getResponse($fp, VER_COMMAND_STATUS))) { |
|
|
|
|
2322
|
|
|
$this->mbPop(); |
2323
|
|
|
return false; |
2324
|
|
|
} |
2325
|
|
|
|
2326
|
|
|
$res = substr($response, 4); // just ignore length, error handling, etc |
|
|
|
|
2327
|
|
|
$p = 0; |
2328
|
|
|
list($rows, $cols) = array_values(unpack('N*N*', substr($response, $p, 8))); |
2329
|
|
|
$p += 8; |
2330
|
|
|
|
2331
|
|
|
$res = array(); |
2332
|
|
|
for ($i = 0; $i < $rows; $i++) { |
2333
|
|
|
for ($j = 0; $j < $cols; $j++) { |
2334
|
|
|
list(, $len) = unpack('N*', substr($response, $p, 4)); |
2335
|
|
|
$p += 4; |
2336
|
|
|
$res[$i][] = substr($response, $p, $len); |
2337
|
|
|
$p += $len; |
2338
|
|
|
} |
2339
|
|
|
} |
2340
|
|
|
|
2341
|
|
|
$this->mbPop(); |
2342
|
|
|
return $res; |
2343
|
|
|
} |
2344
|
|
|
|
2345
|
|
|
////////////////////////////////////////////////////////////////////////// |
2346
|
|
|
// flush |
2347
|
|
|
////////////////////////////////////////////////////////////////////////// |
2348
|
|
|
|
2349
|
|
|
/** |
2350
|
|
|
* @return int |
2351
|
|
|
*/ |
2352
|
|
|
public function flushAttributes() |
2353
|
|
|
{ |
2354
|
|
|
$this->mbPush(); |
2355
|
|
|
if (!($fp = $this->connect())) { |
2356
|
|
|
$this->mbPop(); |
2357
|
|
|
return -1; |
2358
|
|
|
} |
2359
|
|
|
|
2360
|
|
|
$req = pack('nnN', SEARCHD_COMMAND_FLUSHATTRS, VER_COMMAND_FLUSHATTRS, 0); // len=0 |
2361
|
|
View Code Duplication |
if (!$this->send($fp, $req, 8) || !($response = $this->getResponse($fp, VER_COMMAND_FLUSHATTRS))) { |
|
|
|
|
2362
|
|
|
$this->mbPop(); |
2363
|
|
|
return -1; |
2364
|
|
|
} |
2365
|
|
|
|
2366
|
|
|
$tag = -1; |
2367
|
|
|
if (strlen($response) == 4) { |
2368
|
|
|
list(, $tag) = unpack('N*', $response); |
2369
|
|
|
} else { |
2370
|
|
|
$this->error = 'unexpected response length'; |
2371
|
|
|
} |
2372
|
|
|
|
2373
|
|
|
$this->mbPop(); |
2374
|
|
|
return $tag; |
2375
|
|
|
} |
2376
|
|
|
} |
2377
|
|
|
|
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.