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