Verzeichnisstruktur phpBB-3.1.0
- Veröffentlicht
- 27.10.2014
So funktioniert es
|
Auf das letzte Element klicken. Dies geht jeweils ein Schritt zurück |
Auf das Icon klicken, dies öffnet das Verzeichnis. Nochmal klicken schließt das Verzeichnis. |
|
(Beispiel Datei-Icons)
|
Auf das Icon klicken um den Quellcode anzuzeigen |
sphinxapi.php
0001 <?php
0002
0003 //
0004 // $Id: sphinxapi.php 3087 2012-01-30 23:07:35Z shodan $
0005 //
0006
0007 //
0008 // Copyright (c) 2001-2012, Andrew Aksyonoff
0009 // Copyright (c) 2008-2012, Sphinx Technologies Inc
0010 // All rights reserved
0011 //
0012 // This program is free software; you can redistribute it and/or modify
0013 // it under the terms of the GNU General Public License. You should have
0014 // received a copy of the GPL license along with this program; if you
0015 // did not, you can find it at http://www.gnu.org/
0016 //
0017
0018 /////////////////////////////////////////////////////////////////////////////
0019 // PHP version of Sphinx searchd client (PHP API)
0020 /////////////////////////////////////////////////////////////////////////////
0021
0022 /// known searchd commands
0023 define ( "SEARCHD_COMMAND_SEARCH", 0 );
0024 define ( "SEARCHD_COMMAND_EXCERPT", 1 );
0025 define ( "SEARCHD_COMMAND_UPDATE", 2 );
0026 define ( "SEARCHD_COMMAND_KEYWORDS", 3 );
0027 define ( "SEARCHD_COMMAND_PERSIST", 4 );
0028 define ( "SEARCHD_COMMAND_STATUS", 5 );
0029 define ( "SEARCHD_COMMAND_FLUSHATTRS", 7 );
0030
0031 /// current client-side command implementation versions
0032 define ( "VER_COMMAND_SEARCH", 0x119 );
0033 define ( "VER_COMMAND_EXCERPT", 0x104 );
0034 define ( "VER_COMMAND_UPDATE", 0x102 );
0035 define ( "VER_COMMAND_KEYWORDS", 0x100 );
0036 define ( "VER_COMMAND_STATUS", 0x100 );
0037 define ( "VER_COMMAND_QUERY", 0x100 );
0038 define ( "VER_COMMAND_FLUSHATTRS", 0x100 );
0039
0040 /// known searchd status codes
0041 define ( "SEARCHD_OK", 0 );
0042 define ( "SEARCHD_ERROR", 1 );
0043 define ( "SEARCHD_RETRY", 2 );
0044 define ( "SEARCHD_WARNING", 3 );
0045
0046 /// known match modes
0047 define ( "SPH_MATCH_ALL", 0 );
0048 define ( "SPH_MATCH_ANY", 1 );
0049 define ( "SPH_MATCH_PHRASE", 2 );
0050 define ( "SPH_MATCH_BOOLEAN", 3 );
0051 define ( "SPH_MATCH_EXTENDED", 4 );
0052 define ( "SPH_MATCH_FULLSCAN", 5 );
0053 define ( "SPH_MATCH_EXTENDED2", 6 ); // extended engine V2 (TEMPORARY, WILL BE REMOVED)
0054
0055 /// known ranking modes (ext2 only)
0056 define ( "SPH_RANK_PROXIMITY_BM25", 0 ); ///< default mode, phrase proximity major factor and BM25 minor one
0057 define ( "SPH_RANK_BM25", 1 ); ///< statistical mode, BM25 ranking only (faster but worse quality)
0058 define ( "SPH_RANK_NONE", 2 ); ///< no ranking, all matches get a weight of 1
0059 define ( "SPH_RANK_WORDCOUNT", 3 ); ///< simple word-count weighting, rank is a weighted sum of per-field keyword occurence counts
0060 define ( "SPH_RANK_PROXIMITY", 4 );
0061 define ( "SPH_RANK_MATCHANY", 5 );
0062 define ( "SPH_RANK_FIELDMASK", 6 );
0063 define ( "SPH_RANK_SPH04", 7 );
0064 define ( "SPH_RANK_EXPR", 8 );
0065 define ( "SPH_RANK_TOTAL", 9 );
0066
0067 /// known sort modes
0068 define ( "SPH_SORT_RELEVANCE", 0 );
0069 define ( "SPH_SORT_ATTR_DESC", 1 );
0070 define ( "SPH_SORT_ATTR_ASC", 2 );
0071 define ( "SPH_SORT_TIME_SEGMENTS", 3 );
0072 define ( "SPH_SORT_EXTENDED", 4 );
0073 define ( "SPH_SORT_EXPR", 5 );
0074
0075 /// known filter types
0076 define ( "SPH_FILTER_VALUES", 0 );
0077 define ( "SPH_FILTER_RANGE", 1 );
0078 define ( "SPH_FILTER_FLOATRANGE", 2 );
0079
0080 /// known attribute types
0081 define ( "SPH_ATTR_INTEGER", 1 );
0082 define ( "SPH_ATTR_TIMESTAMP", 2 );
0083 define ( "SPH_ATTR_ORDINAL", 3 );
0084 define ( "SPH_ATTR_BOOL", 4 );
0085 define ( "SPH_ATTR_FLOAT", 5 );
0086 define ( "SPH_ATTR_BIGINT", 6 );
0087 define ( "SPH_ATTR_STRING", 7 );
0088 define ( "SPH_ATTR_MULTI", 0x40000001 );
0089 define ( "SPH_ATTR_MULTI64", 0x40000002 );
0090
0091 /// known grouping functions
0092 define ( "SPH_GROUPBY_DAY", 0 );
0093 define ( "SPH_GROUPBY_WEEK", 1 );
0094 define ( "SPH_GROUPBY_MONTH", 2 );
0095 define ( "SPH_GROUPBY_YEAR", 3 );
0096 define ( "SPH_GROUPBY_ATTR", 4 );
0097 define ( "SPH_GROUPBY_ATTRPAIR", 5 );
0098
0099 // important properties of PHP's integers:
0100 // - always signed (one bit short of PHP_INT_SIZE)
0101 // - conversion from string to int is saturated
0102 // - float is double
0103 // - div converts arguments to floats
0104 // - mod converts arguments to ints
0105
0106 // the packing code below works as follows:
0107 // - when we got an int, just pack it
0108 // if performance is a problem, this is the branch users should aim for
0109 //
0110 // - otherwise, we got a number in string form
0111 // this might be due to different reasons, but we assume that this is
0112 // because it didn't fit into PHP int
0113 //
0114 // - factor the string into high and low ints for packing
0115 // - if we have bcmath, then it is used
0116 // - if we don't, we have to do it manually (this is the fun part)
0117 //
0118 // - x64 branch does factoring using ints
0119 // - x32 (ab)uses floats, since we can't fit unsigned 32-bit number into an int
0120 //
0121 // unpacking routines are pretty much the same.
0122 // - return ints if we can
0123 // - otherwise format number into a string
0124
0125 /// pack 64-bit signed
0126 function sphPackI64 ( $v )
0127 {
0128 assert ( is_numeric($v) );
0129
0130 // x64
0131 if ( PHP_INT_SIZE>=8 )
0132 {
0133 $v = (int)$v;
0134 return pack ( "NN", $v>>32, $v&0xFFFFFFFF );
0135 }
0136
0137 // x32, int
0138 if ( is_int($v) )
0139 return pack ( "NN", $v < 0 ? -1 : 0, $v );
0140
0141 // x32, bcmath
0142 if ( function_exists("bcmul") )
0143 {
0144 if ( bccomp ( $v, 0 ) == -1 )
0145 $v = bcadd ( "18446744073709551616", $v );
0146 $h = bcdiv ( $v, "4294967296", 0 );
0147 $l = bcmod ( $v, "4294967296" );
0148 return pack ( "NN", (float)$h, (float)$l ); // conversion to float is intentional; int would lose 31st bit
0149 }
0150
0151 // x32, no-bcmath
0152 $p = max(0, strlen($v) - 13);
0153 $lo = abs((float)substr($v, $p));
0154 $hi = abs((float)substr($v, 0, $p));
0155
0156 $m = $lo + $hi*1316134912.0; // (10 ^ 13) % (1 << 32) = 1316134912
0157 $q = floor($m/4294967296.0);
0158 $l = $m - ($q*4294967296.0);
0159 $h = $hi*2328.0 + $q; // (10 ^ 13) / (1 << 32) = 2328
0160
0161 if ( $v<0 )
0162 {
0163 if ( $l==0 )
0164 $h = 4294967296.0 - $h;
0165 else
0166 {
0167 $h = 4294967295.0 - $h;
0168 $l = 4294967296.0 - $l;
0169 }
0170 }
0171 return pack ( "NN", $h, $l );
0172 }
0173
0174 /// pack 64-bit unsigned
0175 function sphPackU64 ( $v )
0176 {
0177 assert ( is_numeric($v) );
0178
0179 // x64
0180 if ( PHP_INT_SIZE>=8 )
0181 {
0182 assert ( $v>=0 );
0183
0184 // x64, int
0185 if ( is_int($v) )
0186 return pack ( "NN", $v>>32, $v&0xFFFFFFFF );
0187
0188 // x64, bcmath
0189 if ( function_exists("bcmul") )
0190 {
0191 $h = bcdiv ( $v, 4294967296, 0 );
0192 $l = bcmod ( $v, 4294967296 );
0193 return pack ( "NN", $h, $l );
0194 }
0195
0196 // x64, no-bcmath
0197 $p = max ( 0, strlen($v) - 13 );
0198 $lo = (int)substr ( $v, $p );
0199 $hi = (int)substr ( $v, 0, $p );
0200
0201 $m = $lo + $hi*1316134912;
0202 $l = $m % 4294967296;
0203 $h = $hi*2328 + (int)($m/4294967296);
0204
0205 return pack ( "NN", $h, $l );
0206 }
0207
0208 // x32, int
0209 if ( is_int($v) )
0210 return pack ( "NN", 0, $v );
0211
0212 // x32, bcmath
0213 if ( function_exists("bcmul") )
0214 {
0215 $h = bcdiv ( $v, "4294967296", 0 );
0216 $l = bcmod ( $v, "4294967296" );
0217 return pack ( "NN", (float)$h, (float)$l ); // conversion to float is intentional; int would lose 31st bit
0218 }
0219
0220 // x32, no-bcmath
0221 $p = max(0, strlen($v) - 13);
0222 $lo = (float)substr($v, $p);
0223 $hi = (float)substr($v, 0, $p);
0224
0225 $m = $lo + $hi*1316134912.0;
0226 $q = floor($m / 4294967296.0);
0227 $l = $m - ($q * 4294967296.0);
0228 $h = $hi*2328.0 + $q;
0229
0230 return pack ( "NN", $h, $l );
0231 }
0232
0233 // unpack 64-bit unsigned
0234 function sphUnpackU64 ( $v )
0235 {
0236 list ( $hi, $lo ) = array_values ( unpack ( "N*N*", $v ) );
0237
0238 if ( PHP_INT_SIZE>=8 )
0239 {
0240 if ( $hi<0 ) $hi += (1<<32); // because php 5.2.2 to 5.2.5 is totally fucked up again
0241 if ( $lo<0 ) $lo += (1<<32);
0242
0243 // x64, int
0244 if ( $hi<=2147483647 )
0245 return ($hi<<32) + $lo;
0246
0247 // x64, bcmath
0248 if ( function_exists("bcmul") )
0249 return bcadd ( $lo, bcmul ( $hi, "4294967296" ) );
0250
0251 // x64, no-bcmath
0252 $C = 100000;
0253 $h = ((int)($hi / $C) << 32) + (int)($lo / $C);
0254 $l = (($hi % $C) << 32) + ($lo % $C);
0255 if ( $l>$C )
0256 {
0257 $h += (int)($l / $C);
0258 $l = $l % $C;
0259 }
0260
0261 if ( $h==0 )
0262 return $l;
0263 return sprintf ( "%d%05d", $h, $l );
0264 }
0265
0266 // x32, int
0267 if ( $hi==0 )
0268 {
0269 if ( $lo>0 )
0270 return $lo;
0271 return sprintf ( "%u", $lo );
0272 }
0273
0274 $hi = sprintf ( "%u", $hi );
0275 $lo = sprintf ( "%u", $lo );
0276
0277 // x32, bcmath
0278 if ( function_exists("bcmul") )
0279 return bcadd ( $lo, bcmul ( $hi, "4294967296" ) );
0280
0281 // x32, no-bcmath
0282 $hi = (float)$hi;
0283 $lo = (float)$lo;
0284
0285 $q = floor($hi/10000000.0);
0286 $r = $hi - $q*10000000.0;
0287 $m = $lo + $r*4967296.0;
0288 $mq = floor($m/10000000.0);
0289 $l = $m - $mq*10000000.0;
0290 $h = $q*4294967296.0 + $r*429.0 + $mq;
0291
0292 $h = sprintf ( "%.0f", $h );
0293 $l = sprintf ( "%07.0f", $l );
0294 if ( $h=="0" )
0295 return sprintf( "%.0f", (float)$l );
0296 return $h . $l;
0297 }
0298
0299 // unpack 64-bit signed
0300 function sphUnpackI64 ( $v )
0301 {
0302 list ( $hi, $lo ) = array_values ( unpack ( "N*N*", $v ) );
0303
0304 // x64
0305 if ( PHP_INT_SIZE>=8 )
0306 {
0307 if ( $hi<0 ) $hi += (1<<32); // because php 5.2.2 to 5.2.5 is totally fucked up again
0308 if ( $lo<0 ) $lo += (1<<32);
0309
0310 return ($hi<<32) + $lo;
0311 }
0312
0313 // x32, int
0314 if ( $hi==0 )
0315 {
0316 if ( $lo>0 )
0317 return $lo;
0318 return sprintf ( "%u", $lo );
0319 }
0320 // x32, int
0321 elseif ( $hi==-1 )
0322 {
0323 if ( $lo<0 )
0324 return $lo;
0325 return sprintf ( "%.0f", $lo - 4294967296.0 );
0326 }
0327
0328 $neg = "";
0329 $c = 0;
0330 if ( $hi<0 )
0331 {
0332 $hi = ~$hi;
0333 $lo = ~$lo;
0334 $c = 1;
0335 $neg = "-";
0336 }
0337
0338 $hi = sprintf ( "%u", $hi );
0339 $lo = sprintf ( "%u", $lo );
0340
0341 // x32, bcmath
0342 if ( function_exists("bcmul") )
0343 return $neg . bcadd ( bcadd ( $lo, bcmul ( $hi, "4294967296" ) ), $c );
0344
0345 // x32, no-bcmath
0346 $hi = (float)$hi;
0347 $lo = (float)$lo;
0348
0349 $q = floor($hi/10000000.0);
0350 $r = $hi - $q*10000000.0;
0351 $m = $lo + $r*4967296.0;
0352 $mq = floor($m/10000000.0);
0353 $l = $m - $mq*10000000.0 + $c;
0354 $h = $q*4294967296.0 + $r*429.0 + $mq;
0355 if ( $l==10000000 )
0356 {
0357 $l = 0;
0358 $h += 1;
0359 }
0360
0361 $h = sprintf ( "%.0f", $h );
0362 $l = sprintf ( "%07.0f", $l );
0363 if ( $h=="0" )
0364 return $neg . sprintf( "%.0f", (float)$l );
0365 return $neg . $h . $l;
0366 }
0367
0368
0369 function sphFixUint ( $value )
0370 {
0371 if ( PHP_INT_SIZE>=8 )
0372 {
0373 // x64 route, workaround broken unpack() in 5.2.2+
0374 if ( $value<0 ) $value += (1<<32);
0375 return $value;
0376 }
0377 else
0378 {
0379 // x32 route, workaround php signed/unsigned braindamage
0380 return sprintf ( "%u", $value );
0381 }
0382 }
0383
0384
0385 /// sphinx searchd client class
0386 class SphinxClient
0387 {
0388 var $_host; ///< searchd host (default is "localhost")
0389 var $_port; ///< searchd port (default is 9312)
0390 var $_offset; ///< how many records to seek from result-set start (default is 0)
0391 var $_limit; ///< how many records to return from result-set starting at offset (default is 20)
0392 var $_mode; ///< query matching mode (default is SPH_MATCH_ALL)
0393 var $_weights; ///< per-field weights (default is 1 for all fields)
0394 var $_sort; ///< match sorting mode (default is SPH_SORT_RELEVANCE)
0395 var $_sortby; ///< attribute to sort by (defualt is "")
0396 var $_min_id; ///< min ID to match (default is 0, which means no limit)
0397 var $_max_id; ///< max ID to match (default is 0, which means no limit)
0398 var $_filters; ///< search filters
0399 var $_groupby; ///< group-by attribute name
0400 var $_groupfunc; ///< group-by function (to pre-process group-by attribute value with)
0401 var $_groupsort; ///< group-by sorting clause (to sort groups in result set with)
0402 var $_groupdistinct;///< group-by count-distinct attribute
0403 var $_maxmatches; ///< max matches to retrieve
0404 var $_cutoff; ///< cutoff to stop searching at (default is 0)
0405 var $_retrycount; ///< distributed retries count
0406 var $_retrydelay; ///< distributed retries delay
0407 var $_anchor; ///< geographical anchor point
0408 var $_indexweights; ///< per-index weights
0409 var $_ranker; ///< ranking mode (default is SPH_RANK_PROXIMITY_BM25)
0410 var $_rankexpr; ///< ranking mode expression (for SPH_RANK_EXPR)
0411 var $_maxquerytime; ///< max query time, milliseconds (default is 0, do not limit)
0412 var $_fieldweights; ///< per-field-name weights
0413 var $_overrides; ///< per-query attribute values overrides
0414 var $_select; ///< select-list (attributes or expressions, with optional aliases)
0415
0416 var $_error; ///< last error message
0417 var $_warning; ///< last warning message
0418 var $_connerror; ///< connection error vs remote error flag
0419
0420 var $_reqs; ///< requests array for multi-query
0421 var $_mbenc; ///< stored mbstring encoding
0422 var $_arrayresult; ///< whether $result["matches"] should be a hash or an array
0423 var $_timeout; ///< connect timeout
0424
0425 /////////////////////////////////////////////////////////////////////////////
0426 // common stuff
0427 /////////////////////////////////////////////////////////////////////////////
0428
0429 /// create a new client object and fill defaults
0430 function SphinxClient ()
0431 {
0432 // per-client-object settings
0433 $this->_host = "localhost";
0434 $this->_port = 9312;
0435 $this->_path = false;
0436 $this->_socket = false;
0437
0438 // per-query settings
0439 $this->_offset = 0;
0440 $this->_limit = 20;
0441 $this->_mode = SPH_MATCH_ALL;
0442 $this->_weights = array ();
0443 $this->_sort = SPH_SORT_RELEVANCE;
0444 $this->_sortby = "";
0445 $this->_min_id = 0;
0446 $this->_max_id = 0;
0447 $this->_filters = array ();
0448 $this->_groupby = "";
0449 $this->_groupfunc = SPH_GROUPBY_DAY;
0450 $this->_groupsort = "@group desc";
0451 $this->_groupdistinct= "";
0452 $this->_maxmatches = 1000;
0453 $this->_cutoff = 0;
0454 $this->_retrycount = 0;
0455 $this->_retrydelay = 0;
0456 $this->_anchor = array ();
0457 $this->_indexweights= array ();
0458 $this->_ranker = SPH_RANK_PROXIMITY_BM25;
0459 $this->_rankexpr = "";
0460 $this->_maxquerytime= 0;
0461 $this->_fieldweights= array();
0462 $this->_overrides = array();
0463 $this->_select = "*";
0464
0465 $this->_error = ""; // per-reply fields (for single-query case)
0466 $this->_warning = "";
0467 $this->_connerror = false;
0468
0469 $this->_reqs = array (); // requests storage (for multi-query case)
0470 $this->_mbenc = "";
0471 $this->_arrayresult = false;
0472 $this->_timeout = 0;
0473 }
0474
0475 function __destruct()
0476 {
0477 if ( $this->_socket !== false )
0478 fclose ( $this->_socket );
0479 }
0480
0481 /// get last error message (string)
0482 function GetLastError ()
0483 {
0484 return $this->_error;
0485 }
0486
0487 /// get last warning message (string)
0488 function GetLastWarning ()
0489 {
0490 return $this->_warning;
0491 }
0492
0493 /// get last error flag (to tell network connection errors from searchd errors or broken responses)
0494 function IsConnectError()
0495 {
0496 return $this->_connerror;
0497 }
0498
0499 /// set searchd host name (string) and port (integer)
0500 function SetServer ( $host, $port = 0 )
0501 {
0502 assert ( is_string($host) );
0503 if ( $host[0] == '/')
0504 {
0505 $this->_path = 'unix://' . $host;
0506 return;
0507 }
0508 if ( substr ( $host, 0, 7 )=="unix://" )
0509 {
0510 $this->_path = $host;
0511 return;
0512 }
0513
0514 assert ( is_int($port) );
0515 $this->_host = $host;
0516 $this->_port = $port;
0517 $this->_path = '';
0518
0519 }
0520
0521 /// set server connection timeout (0 to remove)
0522 function SetConnectTimeout ( $timeout )
0523 {
0524 assert ( is_numeric($timeout) );
0525 $this->_timeout = $timeout;
0526 }
0527
0528
0529 function _Send ( $handle, $data, $length )
0530 {
0531 if ( feof($handle) || fwrite ( $handle, $data, $length ) !== $length )
0532 {
0533 $this->_error = 'connection unexpectedly closed (timed out?)';
0534 $this->_connerror = true;
0535 return false;
0536 }
0537 return true;
0538 }
0539
0540 /////////////////////////////////////////////////////////////////////////////
0541
0542 /// enter mbstring workaround mode
0543 function _MBPush ()
0544 {
0545 $this->_mbenc = "";
0546 if ( ini_get ( "mbstring.func_overload" ) & 2 )
0547 {
0548 $this->_mbenc = mb_internal_encoding();
0549 mb_internal_encoding ( "latin1" );
0550 }
0551 }
0552
0553 /// leave mbstring workaround mode
0554 function _MBPop ()
0555 {
0556 if ( $this->_mbenc )
0557 mb_internal_encoding ( $this->_mbenc );
0558 }
0559
0560 /// connect to searchd server
0561 function _Connect ()
0562 {
0563 if ( $this->_socket!==false )
0564 {
0565 // we are in persistent connection mode, so we have a socket
0566 // however, need to check whether it's still alive
0567 if ( !@feof ( $this->_socket ) )
0568 return $this->_socket;
0569
0570 // force reopen
0571 $this->_socket = false;
0572 }
0573
0574 $errno = 0;
0575 $errstr = "";
0576 $this->_connerror = false;
0577
0578 if ( $this->_path )
0579 {
0580 $host = $this->_path;
0581 $port = 0;
0582 }
0583 else
0584 {
0585 $host = $this->_host;
0586 $port = $this->_port;
0587 }
0588
0589 if ( $this->_timeout<=0 )
0590 $fp = @fsockopen ( $host, $port, $errno, $errstr );
0591 else
0592 $fp = @fsockopen ( $host, $port, $errno, $errstr, $this->_timeout );
0593
0594 if ( !$fp )
0595 {
0596 if ( $this->_path )
0597 $location = $this->_path;
0598 else
0599 $location = "{$this->_host}:{$this->_port}";
0600
0601 $errstr = trim ( $errstr );
0602 $this->_error = "connection to $location failed (errno=$errno, msg=$errstr)";
0603 $this->_connerror = true;
0604 return false;
0605 }
0606
0607 // send my version
0608 // this is a subtle part. we must do it before (!) reading back from searchd.
0609 // because otherwise under some conditions (reported on FreeBSD for instance)
0610 // TCP stack could throttle write-write-read pattern because of Nagle.
0611 if ( !$this->_Send ( $fp, pack ( "N", 1 ), 4 ) )
0612 {
0613 fclose ( $fp );
0614 $this->_error = "failed to send client protocol version";
0615 return false;
0616 }
0617
0618 // check version
0619 list(,$v) = unpack ( "N*", fread ( $fp, 4 ) );
0620 $v = (int)$v;
0621 if ( $v<1 )
0622 {
0623 fclose ( $fp );
0624 $this->_error = "expected searchd protocol version 1+, got version '$v'";
0625 return false;
0626 }
0627
0628 return $fp;
0629 }
0630
0631 /// get and check response packet from searchd server
0632 function _GetResponse ( $fp, $client_ver )
0633 {
0634 $response = "";
0635 $len = 0;
0636
0637 $header = fread ( $fp, 8 );
0638 if ( strlen($header)==8 )
0639 {
0640 list ( $status, $ver, $len ) = array_values ( unpack ( "n2a/Nb", $header ) );
0641 $left = $len;
0642 while ( $left>0 && !feof($fp) )
0643 {
0644 $chunk = fread ( $fp, min ( 8192, $left ) );
0645 if ( $chunk )
0646 {
0647 $response .= $chunk;
0648 $left -= strlen($chunk);
0649 }
0650 }
0651 }
0652 if ( $this->_socket === false )
0653 fclose ( $fp );
0654
0655 // check response
0656 $read = strlen ( $response );
0657 if ( !$response || $read!=$len )
0658 {
0659 $this->_error = $len
0660 ? "failed to read searchd response (status=$status, ver=$ver, len=$len, read=$read)"
0661 : "received zero-sized searchd response";
0662 return false;
0663 }
0664
0665 // check status
0666 if ( $status==SEARCHD_WARNING )
0667 {
0668 list(,$wlen) = unpack ( "N*", substr ( $response, 0, 4 ) );
0669 $this->_warning = substr ( $response, 4, $wlen );
0670 return substr ( $response, 4+$wlen );
0671 }
0672 if ( $status==SEARCHD_ERROR )
0673 {
0674 $this->_error = "searchd error: " . substr ( $response, 4 );
0675 return false;
0676 }
0677 if ( $status==SEARCHD_RETRY )
0678 {
0679 $this->_error = "temporary searchd error: " . substr ( $response, 4 );
0680 return false;
0681 }
0682 if ( $status!=SEARCHD_OK )
0683 {
0684 $this->_error = "unknown status code '$status'";
0685 return false;
0686 }
0687
0688 // check version
0689 if ( $ver<$client_ver )
0690 {
0691 $this->_warning = sprintf ( "searchd command v.%d.%d older than client's v.%d.%d, some options might not work",
0692 $ver>>8, $ver&0xff, $client_ver>>8, $client_ver&0xff );
0693 }
0694
0695 return $response;
0696 }
0697
0698 /////////////////////////////////////////////////////////////////////////////
0699 // searching
0700 /////////////////////////////////////////////////////////////////////////////
0701
0702 /// set offset and count into result set,
0703 /// and optionally set max-matches and cutoff limits
0704 function SetLimits ( $offset, $limit, $max=0, $cutoff=0 )
0705 {
0706 assert ( is_int($offset) );
0707 assert ( is_int($limit) );
0708 assert ( $offset>=0 );
0709 assert ( $limit>0 );
0710 assert ( $max>=0 );
0711 $this->_offset = $offset;
0712 $this->_limit = $limit;
0713 if ( $max>0 )
0714 $this->_maxmatches = $max;
0715 if ( $cutoff>0 )
0716 $this->_cutoff = $cutoff;
0717 }
0718
0719 /// set maximum query time, in milliseconds, per-index
0720 /// integer, 0 means "do not limit"
0721 function SetMaxQueryTime ( $max )
0722 {
0723 assert ( is_int($max) );
0724 assert ( $max>=0 );
0725 $this->_maxquerytime = $max;
0726 }
0727
0728 /// set matching mode
0729 function SetMatchMode ( $mode )
0730 {
0731 assert ( $mode==SPH_MATCH_ALL
0732 || $mode==SPH_MATCH_ANY
0733 || $mode==SPH_MATCH_PHRASE
0734 || $mode==SPH_MATCH_BOOLEAN
0735 || $mode==SPH_MATCH_EXTENDED
0736 || $mode==SPH_MATCH_FULLSCAN
0737 || $mode==SPH_MATCH_EXTENDED2 );
0738 $this->_mode = $mode;
0739 }
0740
0741 /// set ranking mode
0742 function SetRankingMode ( $ranker, $rankexpr="" )
0743 {
0744 assert ( $ranker>=0 && $ranker<SPH_RANK_TOTAL );
0745 assert ( is_string($rankexpr) );
0746 $this->_ranker = $ranker;
0747 $this->_rankexpr = $rankexpr;
0748 }
0749
0750 /// set matches sorting mode
0751 function SetSortMode ( $mode, $sortby="" )
0752 {
0753 assert (
0754 $mode==SPH_SORT_RELEVANCE ||
0755 $mode==SPH_SORT_ATTR_DESC ||
0756 $mode==SPH_SORT_ATTR_ASC ||
0757 $mode==SPH_SORT_TIME_SEGMENTS ||
0758 $mode==SPH_SORT_EXTENDED ||
0759 $mode==SPH_SORT_EXPR );
0760 assert ( is_string($sortby) );
0761 assert ( $mode==SPH_SORT_RELEVANCE || strlen($sortby)>0 );
0762
0763 $this->_sort = $mode;
0764 $this->_sortby = $sortby;
0765 }
0766
0767 /// bind per-field weights by order
0768 /// DEPRECATED; use SetFieldWeights() instead
0769 function SetWeights ( $weights )
0770 {
0771 assert ( is_array($weights) );
0772 foreach ( $weights as $weight )
0773 assert ( is_int($weight) );
0774
0775 $this->_weights = $weights;
0776 }
0777
0778 /// bind per-field weights by name
0779 function SetFieldWeights ( $weights )
0780 {
0781 assert ( is_array($weights) );
0782 foreach ( $weights as $name=>$weight )
0783 {
0784 assert ( is_string($name) );
0785 assert ( is_int($weight) );
0786 }
0787 $this->_fieldweights = $weights;
0788 }
0789
0790 /// bind per-index weights by name
0791 function SetIndexWeights ( $weights )
0792 {
0793 assert ( is_array($weights) );
0794 foreach ( $weights as $index=>$weight )
0795 {
0796 assert ( is_string($index) );
0797 assert ( is_int($weight) );
0798 }
0799 $this->_indexweights = $weights;
0800 }
0801
0802 /// set IDs range to match
0803 /// only match records if document ID is beetwen $min and $max (inclusive)
0804 function SetIDRange ( $min, $max )
0805 {
0806 assert ( is_numeric($min) );
0807 assert ( is_numeric($max) );
0808 assert ( $min<=$max );
0809 $this->_min_id = $min;
0810 $this->_max_id = $max;
0811 }
0812
0813 /// set values set filter
0814 /// only match records where $attribute value is in given set
0815 function SetFilter ( $attribute, $values, $exclude=false )
0816 {
0817 assert ( is_string($attribute) );
0818 assert ( is_array($values) );
0819 assert ( count($values) );
0820
0821 if ( is_array($values) && count($values) )
0822 {
0823 foreach ( $values as $value )
0824 assert ( is_numeric($value) );
0825
0826 $this->_filters[] = array ( "type"=>SPH_FILTER_VALUES, "attr"=>$attribute, "exclude"=>$exclude, "values"=>$values );
0827 }
0828 }
0829
0830 /// set range filter
0831 /// only match records if $attribute value is beetwen $min and $max (inclusive)
0832 function SetFilterRange ( $attribute, $min, $max, $exclude=false )
0833 {
0834 assert ( is_string($attribute) );
0835 assert ( is_numeric($min) );
0836 assert ( is_numeric($max) );
0837 assert ( $min<=$max );
0838
0839 $this->_filters[] = array ( "type"=>SPH_FILTER_RANGE, "attr"=>$attribute, "exclude"=>$exclude, "min"=>$min, "max"=>$max );
0840 }
0841
0842 /// set float range filter
0843 /// only match records if $attribute value is beetwen $min and $max (inclusive)
0844 function SetFilterFloatRange ( $attribute, $min, $max, $exclude=false )
0845 {
0846 assert ( is_string($attribute) );
0847 assert ( is_float($min) );
0848 assert ( is_float($max) );
0849 assert ( $min<=$max );
0850
0851 $this->_filters[] = array ( "type"=>SPH_FILTER_FLOATRANGE, "attr"=>$attribute, "exclude"=>$exclude, "min"=>$min, "max"=>$max );
0852 }
0853
0854 /// setup anchor point for geosphere distance calculations
0855 /// required to use @geodist in filters and sorting
0856 /// latitude and longitude must be in radians
0857 function SetGeoAnchor ( $attrlat, $attrlong, $lat, $long )
0858 {
0859 assert ( is_string($attrlat) );
0860 assert ( is_string($attrlong) );
0861 assert ( is_float($lat) );
0862 assert ( is_float($long) );
0863
0864 $this->_anchor = array ( "attrlat"=>$attrlat, "attrlong"=>$attrlong, "lat"=>$lat, "long"=>$long );
0865 }
0866
0867 /// set grouping attribute and function
0868 function SetGroupBy ( $attribute, $func, $groupsort="@group desc" )
0869 {
0870 assert ( is_string($attribute) );
0871 assert ( is_string($groupsort) );
0872 assert ( $func==SPH_GROUPBY_DAY
0873 || $func==SPH_GROUPBY_WEEK
0874 || $func==SPH_GROUPBY_MONTH
0875 || $func==SPH_GROUPBY_YEAR
0876 || $func==SPH_GROUPBY_ATTR
0877 || $func==SPH_GROUPBY_ATTRPAIR );
0878
0879 $this->_groupby = $attribute;
0880 $this->_groupfunc = $func;
0881 $this->_groupsort = $groupsort;
0882 }
0883
0884 /// set count-distinct attribute for group-by queries
0885 function SetGroupDistinct ( $attribute )
0886 {
0887 assert ( is_string($attribute) );
0888 $this->_groupdistinct = $attribute;
0889 }
0890
0891 /// set distributed retries count and delay
0892 function SetRetries ( $count, $delay=0 )
0893 {
0894 assert ( is_int($count) && $count>=0 );
0895 assert ( is_int($delay) && $delay>=0 );
0896 $this->_retrycount = $count;
0897 $this->_retrydelay = $delay;
0898 }
0899
0900 /// set result set format (hash or array; hash by default)
0901 /// PHP specific; needed for group-by-MVA result sets that may contain duplicate IDs
0902 function SetArrayResult ( $arrayresult )
0903 {
0904 assert ( is_bool($arrayresult) );
0905 $this->_arrayresult = $arrayresult;
0906 }
0907
0908 /// set attribute values override
0909 /// there can be only one override per attribute
0910 /// $values must be a hash that maps document IDs to attribute values
0911 function SetOverride ( $attrname, $attrtype, $values )
0912 {
0913 assert ( is_string ( $attrname ) );
0914 assert ( in_array ( $attrtype, array ( SPH_ATTR_INTEGER, SPH_ATTR_TIMESTAMP, SPH_ATTR_BOOL, SPH_ATTR_FLOAT, SPH_ATTR_BIGINT ) ) );
0915 assert ( is_array ( $values ) );
0916
0917 $this->_overrides[$attrname] = array ( "attr"=>$attrname, "type"=>$attrtype, "values"=>$values );
0918 }
0919
0920 /// set select-list (attributes or expressions), SQL-like syntax
0921 function SetSelect ( $select )
0922 {
0923 assert ( is_string ( $select ) );
0924 $this->_select = $select;
0925 }
0926
0927 //////////////////////////////////////////////////////////////////////////////
0928
0929 /// clear all filters (for multi-queries)
0930 function ResetFilters ()
0931 {
0932 $this->_filters = array();
0933 $this->_anchor = array();
0934 }
0935
0936 /// clear groupby settings (for multi-queries)
0937 function ResetGroupBy ()
0938 {
0939 $this->_groupby = "";
0940 $this->_groupfunc = SPH_GROUPBY_DAY;
0941 $this->_groupsort = "@group desc";
0942 $this->_groupdistinct= "";
0943 }
0944
0945 /// clear all attribute value overrides (for multi-queries)
0946 function ResetOverrides ()
0947 {
0948 $this->_overrides = array ();
0949 }
0950
0951 //////////////////////////////////////////////////////////////////////////////
0952
0953 /// connect to searchd server, run given search query through given indexes,
0954 /// and return the search results
0955 function Query ( $query, $index="*", $comment="" )
0956 {
0957 assert ( empty($this->_reqs) );
0958
0959 $this->AddQuery ( $query, $index, $comment );
0960 $results = $this->RunQueries ();
0961 $this->_reqs = array (); // just in case it failed too early
0962
0963 if ( !is_array($results) )
0964 return false; // probably network error; error message should be already filled
0965
0966 $this->_error = $results[0]["error"];
0967 $this->_warning = $results[0]["warning"];
0968 if ( $results[0]["status"]==SEARCHD_ERROR )
0969 return false;
0970 else
0971 return $results[0];
0972 }
0973
0974 /// helper to pack floats in network byte order
0975 function _PackFloat ( $f )
0976 {
0977 $t1 = pack ( "f", $f ); // machine order
0978 list(,$t2) = unpack ( "L*", $t1 ); // int in machine order
0979 return pack ( "N", $t2 );
0980 }
0981
0982 /// add query to multi-query batch
0983 /// returns index into results array from RunQueries() call
0984 function AddQuery ( $query, $index="*", $comment="" )
0985 {
0986 // mbstring workaround
0987 $this->_MBPush ();
0988
0989 // build request
0990 $req = pack ( "NNNN", $this->_offset, $this->_limit, $this->_mode, $this->_ranker );
0991 if ( $this->_ranker==SPH_RANK_EXPR )
0992 $req .= pack ( "N", strlen($this->_rankexpr) ) . $this->_rankexpr;
0993 $req .= pack ( "N", $this->_sort ); // (deprecated) sort mode
0994 $req .= pack ( "N", strlen($this->_sortby) ) . $this->_sortby;
0995 $req .= pack ( "N", strlen($query) ) . $query; // query itself
0996 $req .= pack ( "N", count($this->_weights) ); // weights
0997 foreach ( $this->_weights as $weight )
0998 $req .= pack ( "N", (int)$weight );
0999 $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 $res = substr ( $response, 4 ); // just ignore length, error handling, etc
1663 $p = 0;
1664 list ( $rows, $cols ) = array_values ( unpack ( "N*N*", substr ( $response, $p, 8 ) ) ); $p += 8;
1665
1666 $res = array();
1667 for ( $i=0; $i<$rows; $i++ )
1668 for ( $j=0; $j<$cols; $j++ )
1669 {
1670 list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
1671 $res[$i][] = substr ( $response, $p, $len ); $p += $len;
1672 }
1673
1674 $this->_MBPop ();
1675 return $res;
1676 }
1677
1678 //////////////////////////////////////////////////////////////////////////
1679 // flush
1680 //////////////////////////////////////////////////////////////////////////
1681
1682 function FlushAttributes ()
1683 {
1684 $this->_MBPush ();
1685 if (!( $fp = $this->_Connect() ))
1686 {
1687 $this->_MBPop();
1688 return -1;
1689 }
1690
1691 $req = pack ( "nnN", SEARCHD_COMMAND_FLUSHATTRS, VER_COMMAND_FLUSHATTRS, 0 ); // len=0
1692 if ( !( $this->_Send ( $fp, $req, 8 ) ) ||
1693 !( $response = $this->_GetResponse ( $fp, VER_COMMAND_FLUSHATTRS ) ) )
1694 {
1695 $this->_MBPop ();
1696 return -1;
1697 }
1698
1699 $tag = -1;
1700 if ( strlen($response)==4 )
1701 list(,$tag) = unpack ( "N*", $response );
1702 else
1703 $this->_error = "unexpected response length";
1704
1705 $this->_MBPop ();
1706 return $tag;
1707 }
1708 }
1709
1710 //
1711 // $Id: sphinxapi.php 3087 2012-01-30 23:07:35Z shodan $
1712 //
1713