Changeset 22670
- Timestamp:
- 10/01/09 08:26:18 (4 years ago)
- Files:
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
plugins/sfSphinxPlugin/trunk/lib/sfSphinxClient.class.php
r17698 r22670 26 26 const SEARCHD_COMMAND_UPDATE = 2; 27 27 const SEARCHD_COMMAND_KEYWORDS = 3; 28 const SEARCHD_COMMAND_PERSIST = 4; 29 const SEARCHD_COMMAND_STATUS = 5; 30 const SEARCHD_COMMAND_QUERY = 6; 28 31 29 32 // current client-side command implementation versions 30 const VER_COMMAND_SEARCH = 0x11 3;33 const VER_COMMAND_SEARCH = 0x116; 31 34 const VER_COMMAND_EXCERPT = 0x100; 32 const VER_COMMAND_UPDATE = 0x10 1;35 const VER_COMMAND_UPDATE = 0x102; 33 36 const VER_COMMAND_KEYWORDS = 0x100; 37 const VER_COMMAND_STATUS = 0x100; 38 const VER_COMMAND_QUERY = 0x100; 34 39 35 40 // known searchd status codes … … 54 59 const SPH_RANK_WORDCOUNT = 3; // simple word-count weighting, rank is a weighted sum of 55 60 // per-field keyword occurence counts 61 const SPH_RANK_PROXIMITY = 4; 62 const SPH_RANK_MATCHANY = 5; 63 const SPH_RANK_FIELDMASK = 6; 64 56 65 // known sort modes 57 66 const SPH_SORT_RELEVANCE = 0; … … 73 82 const SPH_ATTR_BOOL = 4; 74 83 const SPH_ATTR_FLOAT = 5; 84 const SPH_ATTR_BIGINT = 6; 75 85 const SPH_ATTR_MULTI = 0x40000000; 76 86 … … 110 120 protected $maxquerytime; // max query time, milliseconds (default is 0, do not limit) 111 121 protected $fieldweights; // per-field-name weights 122 protected $overrides; // per-query attribute values overrides 123 protected $select; // select-list (attributes or expressions, with optional aliases) 112 124 protected $mbenc; // stored mbstring encoding 113 125 protected $arrayresult; // whether $result['matches'] should be a hash or an array … … 116 128 private $error; // last error message 117 129 private $warning; // last warning message 130 private $conerror; // connection error vs remote error flag 118 131 private $res; // result from RunQueries() 119 132 … … 127 140 'host' => 'localhost', 128 141 'port' => 3312, 142 'path' => false, 143 'socket' => false, 129 144 'offset' => 0, 130 145 'limit' => 20, … … 149 164 'maxquerytime' => 0, 150 165 'fieldweights' => array(), 166 'overrides' => array(), 167 'select' => '*', 151 168 'mbenc' => '', 152 169 'arrayresult' => true, 153 170 'timeout' => 0, 154 );171 ); 155 172 $available_options = array_keys($default_options); 156 173 $new_options = array_merge($default_options, $options); … … 162 179 } 163 180 } 164 $this->reqs = array(); 165 $this->error = ''; 166 $this->warning = ''; 167 $this->res = false; 168 } 169 170 /** 171 * portably pack numeric to 64 unsigned bits, network order 172 * @param integer $v 173 * @return integer 174 */ 175 private function sphPack64($v) 176 { 177 // x64 route 181 $this->reqs = array(); 182 $this->error = ''; 183 $this->warning = ''; 184 $this->conerror = false; 185 $this->res = false; 186 } 187 188 /** 189 * destructor 190 */ 191 public function __destruct() 192 { 193 if ($this->socket !== false) 194 { 195 fclose($this->socket); 196 } 197 } 198 199 /** 200 * pack 64-bit signed 201 * @param mixed $v integer/float 202 * @return string 203 */ 204 private function sphPackI64($v) 205 { 206 // x64 178 207 if (PHP_INT_SIZE >= 8) 179 208 { 180 $i = (int)$v; 181 return pack('NN', $i >> 32, $i & ((1 << 32) - 1)); 182 } 183 184 // x32 route, bcmath 185 $x = '4294967296'; 209 $v = (int) $v; 210 return pack('NN', $v >> 32, $v&0xFFFFFFFF); 211 } 212 213 // x32, int 214 if (is_int($v)) 215 { 216 return pack('NN', $v < 0 ? -1 : 0, $v); 217 } 218 219 // x32, bcmath 186 220 if (function_exists('bcmul')) 187 221 { 188 $h = bcdiv($v, $x, 0); 189 $l = bcmod($v, $x); 222 if (bccomp($v, 0) == -1) 223 { 224 $v = bcadd('18446744073709551616', $v); 225 } 226 $h = bcdiv($v, '4294967296', 0); 227 $l = bcmod($v, '4294967296'); 190 228 // conversion to float is intentional; int would lose 31st bit 191 return pack('NN', (float)$h, (float)$l); 192 } 193 194 // x32 route, 15 or less decimal digits 195 // we can use float, because its actually double and has 52 precision bits 196 if (strlen($v) <= 15) 197 { 198 $f = (float)$v; 199 $h = (int)($f / $x); 200 $l = (int)($f - $x * $h); 201 return pack('NN', $h, $l); 202 } 203 } 204 205 /** 206 * portably unpack 64 unsigned bits, network order to numeric 207 * @param integer $v 208 * @return integer 209 */ 210 private function sphUnpack64($v) 211 { 212 list($h, $l) = array_values(unpack('N*N*', $v)); 213 // x64 route 229 return pack('NN', (float) $h, (float) $l); 230 } 231 232 // x32, no-bcmath 233 $p = max(0, strlen($v) - 13); 234 $lo = abs((float) substr($v, $p)); 235 $hi = abs((float) substr($v, 0, $p)); 236 237 $m = $lo + $hi*1316134912.0; // (10 ^ 13) % (1 << 32) = 1316134912 238 $q = floor($m / 4294967296.0); 239 $l = $m - ($q * 4294967296.0); 240 $h = $hi * 2328.0 + $q; // (10 ^ 13) / (1 << 32) = 2328 241 242 if ($v < 0) 243 { 244 if ($l == 0) 245 { 246 $h = 4294967296.0 - $h; 247 } 248 else 249 { 250 $h = 4294967295.0 - $h; 251 $l = 4294967296.0 - $l; 252 } 253 } 254 return pack('NN', $h, $l); 255 } 256 257 /** 258 * pack 64-bit unsigned 259 * @param mixed $v integer/float 260 * @return string 261 */ 262 public function sphPackU64($v) 263 { 264 // x64 214 265 if (PHP_INT_SIZE >= 8) 215 266 { 216 if ($h < 0) 217 { 218 $h += (1 << 32); // because php 5.2.2 to 5.2.5 is totally messed up again 219 } 220 if ($l < 0) 221 { 222 $l += (1 << 32); 223 } 224 return ($h << 32) + $l; 225 } 226 227 // x32 route 228 $h = sprintf('%u', $h); 229 $l = sprintf('%u', $l); 230 $x = '4294967296'; 231 232 // bcmath 267 // x64, int 268 if (is_int($v)) 269 { 270 return pack('NN', $v>>32, $v&0xFFFFFFFF); 271 } 272 273 // x64, bcmath 274 if ( function_exists('bcmul')) 275 { 276 $h = bcdiv($v, 4294967296, 0); 277 $l = bcmod($v, 4294967296); 278 return pack('NN', $h, $l); 279 } 280 281 // x64, no-bcmath 282 $p = max(0, strlen($v) - 13); 283 $lo = (int) substr($v, $p); 284 $hi = (int) substr($v, 0, $p); 285 286 $m = $lo + $hi * 1316134912; 287 $l = $m % 4294967296; 288 $h = $hi * 2328 + (int) ($m / 4294967296); 289 290 return pack('NN', $h, $l ); 291 } 292 293 // x32, int 294 if (is_int($v)) 295 { 296 return pack('NN', 0, $v); 297 } 298 299 // x32, bcmath 233 300 if (function_exists('bcmul')) 234 301 { 235 return bcadd($l, bcmul($x, $h)); 236 } 237 238 // no bcmath, 15 or less decimal digits 239 // we can use float, because its actually double and has 52 precision bits 240 if ($h < 1048576) 241 { 242 $f = ((float)$h) * $x + (float)$l; 243 return sprintf('%.0f', $f); // builtin conversion is only about 39-40 bits precise! 244 } 302 $h = bcdiv($v, '4294967296', 0); 303 $l = bcmod($v, '4294967296'); 304 // conversion to float is intentional; int would lose 31st bit 305 return pack('NN', (float) $h, (float) $l); 306 } 307 308 // x32, no-bcmath 309 $p = max(0, strlen($v) - 13); 310 $lo = (float) substr($v, $p); 311 $hi = (float) substr($v, 0, $p); 312 313 $m = $lo + $hi * 1316134912.0; 314 $q = floor($m / 4294967296.0); 315 $l = $m - ($q * 4294967296.0); 316 $h = $hi * 2328.0 + $q; 317 318 return pack('NN', $h, $l); 319 } 320 321 /** 322 * unpack 64-bit unsigned 323 * @param mixed $v integer/float 324 * @return string 325 */ 326 private function sphUnpackU64($v) 327 { 328 list($hi, $lo) = array_values(unpack('N*N*', $v)); 329 330 if (PHP_INT_SIZE >= 8) 331 { 332 if ( $hi < 0 ) 333 { 334 $hi += (1 << 32); // because php 5.2.2 to 5.2.5 is totally fucked up again 335 } 336 if ( $lo < 0 ) 337 { 338 $lo += (1 << 32); 339 } 340 341 // x64, int 342 if ($hi <= 2147483647) 343 { 344 return ($hi << 32) + $lo; 345 } 346 347 // x64, bcmath 348 if (function_exists('bcmul')) 349 { 350 return bcadd($lo, bcmul($hi, '4294967296')); 351 } 352 353 // x64, no-bcmath 354 $C = 100000; 355 $h = ((int) ($hi / $C) << 32) + (int)($lo / $C); 356 $l = (($hi % $C) << 32) + ($lo % $C); 357 if ($l > $C) 358 { 359 $h += (int) ($l / $C); 360 $l = $l % $C; 361 } 362 363 if ($h == 0) 364 { 365 return $l; 366 } 367 return sprintf('%d%05d', $h, $l); 368 } 369 370 // x32, int 371 if ($hi == 0) 372 { 373 if ($lo > 0) 374 { 375 return $lo; 376 } 377 return sprintf('%u', $lo ); 378 } 379 380 $hi = sprintf('%u', $hi ); 381 $lo = sprintf('%u', $lo ); 382 383 // x32, bcmath 384 if (function_exists('bcmul')) 385 { 386 return bcadd($lo, bcmul($hi, '4294967296')); 387 } 388 389 // x32, no-bcmath 390 $hi = (float) $hi; 391 $lo = (float) $lo; 392 393 $q = floor($hi / 10000000.0); 394 $r = $hi - $q * 10000000.0; 395 $m = $lo + $r * 4967296.0; 396 $mq = floor($m / 10000000.0); 397 $l = $m - $mq * 10000000.0; 398 $h = $q * 4294967296.0 + $r * 429.0 + $mq; 399 400 $h = sprintf('%.0f', $h ); 401 $l = sprintf('%07.0f', $l ); 402 if ($h == '0') 403 { 404 return sprintf('%.0f', (float) $l); 405 } 406 return $h . $l; 407 } 408 409 /** 410 * unpack 64-bit signed 411 */ 412 private function sphUnpackI64($v) 413 { 414 list($hi, $lo) = array_values(unpack('N*N*', $v)); 415 416 // x64 417 if ( PHP_INT_SIZE>=8 ) 418 { 419 if ( $hi<0 ) 420 { 421 $hi += (1 << 32); // because php 5.2.2 to 5.2.5 is totally fucked up again 422 } 423 if ( $lo<0 ) 424 { 425 $lo += (1 << 32); 426 } 427 428 return ($hi << 32) + $lo; 429 } 430 431 // x32, int 432 if ($hi == 0) 433 { 434 if ($lo > 0) 435 { 436 return $lo; 437 } 438 return sprintf('%u', $lo); 439 } 440 // x32, int 441 elseif ($hi == -1) 442 { 443 if ($lo < 0) 444 { 445 return $lo; 446 } 447 return sprintf('%.0f', $lo - 4294967296.0); 448 } 449 450 $neg = ''; 451 $c = 0; 452 if ($hi < 0) 453 { 454 $hi = ~$hi; 455 $lo = ~$lo; 456 $c = 1; 457 $neg = '-'; 458 } 459 460 $hi = sprintf('%u', $hi ); 461 $lo = sprintf('%u', $lo ); 462 463 // x32, bcmath 464 if ( function_exists('bcmul') ) 465 { 466 return $neg . bcadd(bcadd($lo, bcmul($hi, '4294967296')), $c); 467 } 468 469 // x32, no-bcmath 470 $hi = (float) $hi; 471 $lo = (float) $lo; 472 473 $q = floor($hi / 10000000.0); 474 $r = $hi - $q * 10000000.0; 475 $m = $lo + $r * 4967296.0; 476 $mq = floor($m / 10000000.0); 477 $l = $m - $mq * 10000000.0 + $c; 478 $h = $q * 4294967296.0 + $r * 429.0 + $mq; 479 480 $h = sprintf('%.0f', $h ); 481 $l = sprintf('%07.0f', $l ); 482 if ($h == '0') 483 { 484 return $neg . sprintf('%.0f', (float) $l); 485 } 486 return $neg . $h . $l; 245 487 } 246 488 … … 288 530 private function EscapeString($string) 289 531 { 290 $from = array(' (', ')', '|', '-', '!', '@', '~', '"', '&', '/');291 $to = array('\ (', '\)', '\|', '\-', '\!', '\@', '\~', '\"', '\&', '\/');532 $from = array('\\', '(',')','|','-','!','@','~','"','&', '/', '^', '$', '='); 533 $to = array('\\\\', '\(','\)','\|','\-','\!','\@','\~','\"', '\&', '\/', '\^', '\$', '\='); 292 534 return str_replace($from, $to, $string); 293 535 } … … 318 560 { 319 561 return $this->warning; 562 } 563 564 /** 565 * get last error flag 566 * (to tell network connection errors from searchd errors or broken responses) 567 * @return mixed 568 */ 569 public function isConnectError() 570 { 571 return $this->conerror; 320 572 } 321 573 … … 328 580 { 329 581 $this->host = $host; 582 if ($host[0] == '/') 583 { 584 $this->path = 'unix://' . $host; 585 return; 586 } 587 if (substr($host, 0, 7) == 'unix://') 588 { 589 $this->path = $host; 590 return; 591 } 330 592 $this->port = $port; 593 $this->path = ''; 594 } 595 596 /** 597 * set server connection timeout (0 to remove) 598 * @param integer $timeout 599 */ 600 public function SetConnectTimeout($timeout) 601 { 602 $this->timeout = $timeout; 603 } 604 605 protected function Send($handle, $data, $length) 606 { 607 if (feof($handle) || fwrite($handle, $data, $length) !== $length) 608 { 609 $this->error = 'connection unexpectedly closed (timed out?)'; 610 $this->connerror = true; 611 return false; 612 } 613 return true; 331 614 } 332 615 … … 338 621 protected function Connect() 339 622 { 623 if ($this->socket !== false) 624 { 625 return $this->socket; 626 } 627 340 628 $errno = 0; 341 629 $errstr = ''; 630 $this->connerror = false; 631 632 if ($this->path) 633 { 634 $host = $this->path; 635 $port = 0; 636 } 637 else 638 { 639 $host = $this->host; 640 $port = $this->port; 641 } 642 342 643 if ($this->timeout <= 0) 343 644 { 344 $fp = @fsock open($this->host, $this->port, $errno, $errstr);645 $fp = @fsock($host, $port, $errno, $errstr); 345 646 } 346 647 else 347 648 { 348 $fp = @fsockopen($this->host, $this->port, $errno, $errstr, $this->timeout); 349 } 649 $fp = @fsockopen($host, $port, $errno, $errstr, $this->timeout); 650 } 651 350 652 if (!$fp) 351 653 { 654 if($this->path) 655 { 656 $location = $this->path; 657 } 658 else 659 { 660 $location = "{$this->host}:{$this->port}"; 661 } 662 352 663 $errstr = trim($errstr); 353 $this->error = "Sphinx connection to {$this->host}:{$this->port} failed (errno=$errno, msg=$errstr)"; 354 throw new Exception($this->error); 664 $this->error = "connection to $location failed (errno=$errno, msg=$errstr)"; 665 $this->connerror = true; 666 return false; 667 } 668 669 // send my version 670 // this is a subtle part. we must do it before (!) reading back from searchd. 671 // because otherwise under some conditions (reported on FreeBSD for instance) 672 // TCP stack could throttle write-write-read pattern because of Nagle. 673 if (!$this->Send($fp, pack('N', 1), 4)) 674 { 675 fclose($fp); 676 $this->error = 'failed to send client protocol version'; 677 return false; 355 678 } 356 679 357 680 // check version 358 list(, $v) = unpack( "N*", fread($fp, 4));681 list(, $v) = unpack('N*', fread($fp, 4)); 359 682 $v = (int) $v; 360 683 if ($v < 1) … … 362 685 fclose($fp); 363 686 $this->error = "expected searchd protocol version 1+, got version '$v'"; 364 throw new Exception($this->error); 365 } 366 367 // all ok, send my version 368 fwrite($fp, pack("N", 1)); 687 return false; 688 } 689 369 690 return $fp; 370 691 } … … 392 713 } 393 714 } 394 fclose($fp); 715 if ($this->socket === false) 716 { 717 fclose($fp); 718 } 395 719 396 720 // check response … … 449 773 450 774 /** 775 * set maximum query time, in milliseconds, per-index 776 * integer, 0 means "do not limit" 777 * @param integer $max 778 */ 779 public function SetMaxQueryTime($max) 780 { 781 $this->maxquerytime = $max; 782 } 783 784 /** 451 785 * set match mode 452 786 * @param integer $mode … … 455 789 { 456 790 $this->mode = $mode; 791 } 792 793 /** 794 * set ranking mode 795 * @param integer $ranker 796 */ 797 public function SetRankingMode($ranker) 798 { 799 $this->ranker = $ranker; 457 800 } 458 801 … … 470 813 /** 471 814 * set per-field weights 815 * @deprecated use SetFieldWeights() instead 472 816 * @param array $weights 473 817 */ … … 478 822 $this->weights = $weights; 479 823 } 824 } 825 826 /** 827 * bind per-field weights by name 828 * @param array $weights 829 */ 830 public function SetFieldWeights(array $weights) 831 { 832 $this->fieldweights = $weights; 833 } 834 835 /** 836 * bind per-index weights by name 837 * @param array $weights 838 */ 839 public function SetIndexWeights(array $weights) 840 { 841 $this->indexweights = $weights; 480 842 } 481 843 … … 500 862 * @param boolean $exclude 501 863 */ 502 public function SetFilter($attribute, $values, $exclude = false)864 public function SetFilter($attribute, array $values, $exclude = false) 503 865 { 504 866 if (is_array($values) && !empty($values)) … … 530 892 'min' => $min, 531 893 'max' => $max 894 ); 895 } 896 897 /** 898 * set float range filter 899 * only match records if $attribute value is beetwen $min and $max (inclusive) 900 * @param string $attribute 901 * @param integer $max 902 * @param integer $min 903 * @param boolean $exclude 904 */ 905 public function SetFilterFloatRange($attribute, $min, $max, $exclude = false) 906 { 907 $this->filters[] = array( 908 'type' => self::SPH_FILTER_FLOATRANGE, 909 'attr' => $attribute, 910 'exclude' => $exclude, 911 'min' => $min, 912 'max' => $max 913 ); 914 } 915 916 /** 917 * setup anchor point for geosphere distance calculations 918 * required to use @geodist in filters and sorting 919 * latitude and longitude must be in radians 920 * @param string $attrlat 921 * @param string $attrlong 922 * @param float $lat 923 * @param float $long 924 */ 925 public function SetGeoAnchor($attrlat, $attrlong, $lat, $long) 926 { 927 $this->anchor = array( 928 'attrlat' => $attrlat, 929 'attrlong' => $attrlong, 930 'lat' => $lat, 931 'long' => $long 532 932 ); 533 933 } … … 553 953 * @param string $attribute 554 954 * @param integer $func 555 */ 556 public function SetGroupBy($attribute, $func) 955 * @apram string $groupsort 956 */ 957 public function SetGroupBy($attribute, $func, $groupsort = '@group desc') 557 958 { 558 959 $this->groupby = $attribute; 559 960 $this->groupfunc = $func; 961 $this->groupsort = $groupsort; 962 } 963 964 /** 965 * set count-distinct attribute for group-by queries 966 * @param string $attribute 967 */ 968 public function SetGroupDistinct($attribute) 969 { 970 $this->groupdistinct = $attribute; 971 } 972 973 /** 974 * set distributed retries count and delay 975 * @param integer $count 976 * @param integer $delay 977 */ 978 public function SetRetries($count, $delay = 0) 979 { 980 $this->retrycount = $count; 981 $this->retrydelay = $delay; 982 } 983 984 /** 985 * set result set format (hash or array; hash by default) 986 * PHP specific; needed for group-by-MVA result sets that may contain duplicate IDs 987 * @param boolean $arrayresult 988 */ 989 public function SetArrayResult($arrayresult) 990 { 991 $this->arrayresult = $arrayresult; 992 } 993 994 /** 995 * set attribute values override 996 * there can be only one override per attribute 997 * $values must be a hash that maps document IDs to attribute values 998 * @param string $attrname 999 * @param integer $attrtype 1000 * @param array $values 1001 */ 1002 public function SetOverride($attrname, $attrtype, array $values) 1003 { 1004 $this->overrides[$attrname] = array( 1005 'attr' => $attrname, 1006 'type' => $attrtype, 1007 'values' => $values 1008 ); 1009 } 1010 1011 /** 1012 * set select-list (attributes or expressions), SQL-like syntax 1013 * @param string $select 1014 */ 1015 public function SetSelect($select) 1016 { 1017 $this->select = $select; 1018 } 1019 1020 /** 1021 * clear all filters (for multi-queries) 1022 */ 1023 public function ResetFilters() 1024 { 1025 $this->filters = array(); 1026 $this->anchor = array(); 1027 } 1028 1029 /** 1030 * clear groupby settings (for multi-queries) 1031 */ 1032 public function ResetGroupBy() 1033 { 1034 $this->groupby = ''; 1035 $this->groupfunc = self::SPH_GROUPBY_DAY; 1036 $this->groupsort = '@group desc'; 1037 $this->groupdistinct = ''; 1038 } 1039 1040 /** 1041 * clear all attribute value overrides (for multi-queries) 1042 */ 1043 public function ResetOverrides() 1044 { 1045 $this->overrides = array(); 560 1046 } 561 1047 … … 584 1070 $req .= pack('N', strlen($index)) . $index; // indexes 585 1071 $req .= pack('N', 1); // id64 range marker 586 $req .= $this->sphPack 64($this->min_id) . $this->sphPack64($this->max_id); // id64 range1072 $req .= $this->sphPackU64($this->min_id) . $this->sphPackU64($this->max_id); // id64 range 587 1073 588 1074 // filters … … 598 1084 foreach ($filter['values'] as $value) 599 1085 { 600 $req .= pack('N', floatval($value)); // this uberhack is to workaround 32bit signed int limit on x32 platforms1086 $req .= sphPackI64($value); 601 1087 } 602 1088 break; 603 1089 case self::SPH_FILTER_RANGE: 604 $req .= pack('NN', $filter['min'],$filter['max']);1090 $req .= sphPackI64($filter['min']) . sphPackI64($filter['max']); 605 1091 break; 606 1092 case self::SPH_FILTER_FLOATRANGE: 607 $req .= $this->packFloat( $filter['min']) . $this->packFloat($filter['max']);1093 $req .= $this->packFloat($filter['min']) . $this->packFloat($filter['max']); 608 1094 } 609 1095 $req .= pack('N', $filter['exclude']); … … 643 1129 // per-field weights 644 1130 $req .= pack('N', count($this->fieldweights)); 645 foreach ( $this->fieldweights as $field=>$weight)1131 foreach ($this->fieldweights as $field=>$weight) 646 1132 $req .= pack('N', strlen($field)) . $field . pack('N', $weight); 647 1133 648 1134 // comment 649 1135 $req .= pack('N', strlen($comment)) . $comment; 1136 1137 // attribute overrides 1138 $req .= pack('N', count($this->overrides)); 1139 foreach ($this->overrides as $key => $entry) 1140 { 1141 $req .= pack('N', strlen($entry['attr'])) . $entry['attr']; 1142 $req .= pack('NN', $entry['type'], count($entry['values'])); 1143 foreach ($entry['values'] as $id => $val) 1144 { 1145 $req .= sphPackU64($id); 1146 switch ($entry['type']) 1147 { 1148 case self::SPH_ATTR_FLOAT: 1149 $req .= $this->PackFloat($val); 1150 break; 1151 case self::SPH_ATTR_BIGINT: 1152 $req .= sphPackI64($val); 1153 break; 1154 default: 1155 $req .= pack('N', $val); 1156 } 1157 } 1158 } 1159 1160 // select-list 1161 $req .= pack('N', strlen($this->select)) . $this->select; 650 1162 651 1163 // mbstring workaround … … 731 1243 732 1244 $nreqs = count($this->reqs); 733 $req = join ( '', $this->reqs);1245 $req = join('', $this->reqs); 734 1246 $len = 4 + strlen($req); 735 1247 // add header 736 1248 $req = pack('nnNN', self::SEARCHD_COMMAND_SEARCH, self::VER_COMMAND_SEARCH, $len, $nreqs) . $req; 737 1249 738 fwrite ($fp, $req, $len + 8); 739 if (!($response = $this->GetResponse($fp, self::VER_COMMAND_SEARCH))) 1250 if (!($this->Send($fp, $req, $len + 8)) || !($response = $this->GetResponse($fp, VER_COMMAND_SEARCH))) 740 1251 { 741 1252 $this->MBPop(); … … 744 1255 745 1256 $this->reqs = array(); 1257 1258 return $this->ParseSearchResponse($response, $nreqs); 1259 } 1260 1261 /** 1262 * parse and return search query (or queries) response 1263 * @param string $response 1264 * @param integer $nreqs 1265 */ 1266 protected function ParseSearchResponse($response, $nreqs) 1267 { 746 1268 747 1269 //// parse response … … 825 1347 if ($id64) 826 1348 { 827 $doc = $this->sphUnpack 64(substr($response, $p, 8));1349 $doc = $this->sphUnpackU64(substr($response, $p, 8)); 828 1350 $p += 8; 829 1351 list(, $weight) = unpack('N*', substr($response, $p, 4)); … … 865 1387 foreach ($attrs as $attr => $type) 866 1388 { 1389 // handle 64bit ints 1390 if ($type == self::SPH_ATTR_BIGINT) 1391 { 1392 $attrvals[$attr] = sphUnpackI64(substr($response, $p, 8)); 1393 $p += 8; 1394 continue; 1395 } 1396 867 1397 // handle floats 868 1398 if ($type == self::SPH_ATTR_FLOAT) … … 870 1400 list(, $uval) = unpack('N*', substr($response, $p, 4)); 871 1401 $p += 4; 872 list(, $fval) = unpack('f*', pack('L', $uval ));1402 list(, $fval) = unpack('f*', pack('L', $uval)); 873 1403 $attrvals[$attr] = $fval; 874 1404 continue; … … 905 1435 } 906 1436 907 list($total, $total_found, $msecs, $words ) = 908 array_values(unpack('N*N*N*N*', substr($response, $p, 16))); 1437 list($total, $total_found, $msecs, $words) = array_values(unpack('N*N*N*N*', substr($response, $p, 16))); 909 1438 $result['total'] = sprintf('%u', $total); 910 1439 $result['total_found'] = sprintf('%u', $total_found); … … 1004 1533 // add header 1005 1534 $req = pack('nnN', self::SEARCHD_COMMAND_EXCERPT, self::VER_COMMAND_EXCERPT, $len) . $req; 1006 $wrote = fwrite($fp, $req, $len+8); 1007 if (!($response = $this->GetResponse($fp, self::VER_COMMAND_EXCERPT))) 1535 if (!($this->Send($fp, $req, $len + 8)) || !($response = $this->GetResponse($fp, self::VER_COMMAND_EXCERPT))) 1008 1536 { 1009 1537 $this->MBPop(); … … 1058 1586 $req = pack('N', strlen($query)) . $query; // req query 1059 1587 $req .= pack('N', strlen($index)) . $index; // req index 1060 $req .= pack('N', (int)$hits );1588 $req .= pack('N', (int)$hits); 1061 1589 1062 1590 //// send query, get response … … 1064 1592 $len = strlen($req); 1065 1593 $req = pack('nnN', self::SEARCHD_COMMAND_KEYWORDS, self::VER_COMMAND_KEYWORDS, $len) . $req; // add header 1066 $wrote = fwrite ($fp, $req, $len + 8); 1067 if (!($response = $this->_GetResponse($fp, self::VER_COMMAND_KEYWORDS))) 1594 if (!($this->Send($fp, $req, $len + 8)) || !($response = $this->GetResponse($fp, self::VER_COMMAND_KEYWORDS))) 1068 1595 { 1069 1596 $this->MBPop(); … … 1078 1605 list(, $nwords) = unpack('N*', substr($response, $pos, 4)); 1079 1606 $pos += 4; 1080 for ( $i=0; $i<$nwords; $i++)1607 for($i = 0; $i < $nwords; $i ++) 1081 1608 { 1082 1609 list(, $len) = unpack('N*', substr($response, $pos, 4)); 1083 1610 $pos += 4; 1084 $tokenized = $len ? substr ( $response, $pos, $len) : '';1611 $tokenized = $len ? substr($response, $pos, $len) : ''; 1085 1612 $pos += $len; 1086 1613 1087 1614 list(, $len) = unpack('N*', substr($response, $pos, 4)); 1088 1615 $pos += 4; 1089 $normalized = $len ? substr ( $response, $pos, $len) : '';1616 $normalized = $len ? substr($response, $pos, $len) : ''; 1090 1617 $pos += $len; 1091 1618 … … 1117 1644 * @param array $attrs 1118 1645 * @param array $values 1646 * @param boolean $mva 1119 1647 * @return integer amount of updated documents (0 or more) on success, or -1 on failure 1120 1648 */ 1121 public function UpdateAttributes($index, $attrs, $values)1649 public function UpdateAttributes($index, array $attrs, array $values, $mva = false) 1122 1650 { 1123 1651 // build request … … 1125 1653 1126 1654 $req .= pack('N', count($attrs)); 1127 foreach ( $attrs as $attr)1655 foreach ($attrs as $attr) 1128 1656 { 1129 1657 $req .= pack('N', strlen($attr)) . $attr; 1658 $req .= pack('N', $mva ? 1 : 0); 1130 1659 } 1131 1660 … … 1133 1662 foreach ($values as $id => $entry) 1134 1663 { 1135 $req .= $this->sphPack 64($id);1664 $req .= $this->sphPackU64($id); 1136 1665 foreach ($entry as $v) 1137 1666 { 1138 $req .= pack('N', $v); 1667 $req .= pack('N', $mva ? count($v) : $v); 1668 if ($mva) 1669 { 1670 foreach ($v as $vv) 1671 { 1672 $req .= pack('N', $vv); 1673 } 1674 } 1139 1675 } 1140 1676 } … … 1144 1680 1145 1681 // connect, send query, get response 1146 if (!($fp = $this-> _Connect()))1682 if (!($fp = $this->Connect())) 1147 1683 { 1148 1684 $this->MBPop(); … … 1152 1688 $len = strlen($req); 1153 1689 // add header 1154 $req = pack('nnN', self::SEARCHD_COMMAND_UPDATE, self::VER_COMMAND_UPDATE, $len ) . $req; 1155 fwrite ( $fp, $req, $len+8 ); 1156 1157 if (!($response = $this->_GetResponse ( $fp, self::VER_COMMAND_UPDATE ))) 1158 { 1159 $this->MBPop(); 1690 $req = pack('nnN', self::SEARCHD_COMMAND_UPDATE, self::VER_COMMAND_UPDATE, $len) . $req; 1691 if (!$this->Send($fp, $req, $len + 8)) 1692 { 1693 return -1; 1694 } 1695 1696 if (!($response = $this->GetResponse($fp, self::VER_COMMAND_UPDATE))) 1697 { 1160 1698 return -1; 1161 1699 } … … 1163 1701 // parse response 1164 1702 list(, $updated) = unpack('N*', substr($response, 0, 4)); 1703 1704 return $updated; 1705 } 1706 1707 /** 1708 * open a persistent connection 1709 * @return boolean 1710 */ 1711 public function Open() 1712 { 1713 if ($this->socket !== false) 1714 { 1715 $this->error = 'already connected'; 1716 return false; 1717 } 1718 if (!$fp = $this->Connect()) 1719 { 1720 return false; 1721 } 1722 1723 // command, command version = 0, body length = 4, body = 1 1724 $req = pack('nnNN', SEARCHD_COMMAND_PERSIST, 0, 4, 1); 1725 if (!$this->Send ($fp, $req, 12)) 1726 { 1727 return false; 1728 } 1729 1730 $this->socket = $fp; 1731 return true; 1732 } 1733 1734 /** 1735 * close a persistent connection 1736 * @return boolean 1737 */ 1738 public function Close() 1739 { 1740 if ($this->socket === false) 1741 { 1742 $this->error = 'not connected'; 1743 return false; 1744 } 1745 1746 fclose($this->socket); 1747 $this->socket = false; 1748 1749 return true; 1750 } 1751 1752 /** 1753 * status 1754 * @return array 1755 */ 1756 public function Status() 1757 { 1758 $this->MBPush(); 1759 if (!($fp = $this->Connect())) 1760 { 1761 $this->MBPop(); 1762 return false; 1763 } 1764 1765 $req = pack('nnNN', SEARCHD_COMMAND_STATUS, VER_COMMAND_STATUS, 4, 1); // len=4, body=1 1766 if (!($this->Send ($fp, $req, 12)) || !($response = $this->GetResponse ($fp, VER_COMMAND_STATUS))) 1767 { 1768 $this->MBPop(); 1769 return false; 1770 } 1771 1772 $res = substr($response, 4); // just ignore length, error handling, etc 1773 $p = 0; 1774 list($rows, $cols) = array_values (unpack ('N*N*', substr ($response, $p, 8))); $p += 8; 1775 1776 $res = array(); 1777 for ($i = 0; $i < $rows; $i ++) 1778 { 1779 for ($j = 0; $j < $cols; $j ++) 1780 { 1781 list(,$len) = unpack('N*', substr($response, $p, 4)); 1782 $p += 4; 1783 $res[$i][] = substr($response, $p, $len); 1784 $p += $len; 1785 } 1786 } 1787 1165 1788 $this->MBPop(); 1166 return $ updated;1789 return $res; 1167 1790 } 1168 1791