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