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