10Syaroslav@ivinco.com<?php
20Syaroslav@ivinco.com
30Syaroslav@ivinco.com//
40Syaroslav@ivinco.com// $Id: sphinxapi.php 2055 2009-11-06 23:09:58Z shodan $
50Syaroslav@ivinco.com//
60Syaroslav@ivinco.com
70Syaroslav@ivinco.com//
80Syaroslav@ivinco.com// Copyright (c) 2001-2008, Andrew Aksyonoff. All rights reserved.
90Syaroslav@ivinco.com//
100Syaroslav@ivinco.com// This program is free software; you can redistribute it and/or modify
110Syaroslav@ivinco.com// it under the terms of the GNU General Public License. You should have
120Syaroslav@ivinco.com// received a copy of the GPL license along with this program; if you
130Syaroslav@ivinco.com// did not, you can find it at http://www.gnu.org/
140Syaroslav@ivinco.com//
150Syaroslav@ivinco.com
160Syaroslav@ivinco.com/////////////////////////////////////////////////////////////////////////////
170Syaroslav@ivinco.com// PHP version of Sphinx searchd client (PHP API)
180Syaroslav@ivinco.com/////////////////////////////////////////////////////////////////////////////
190Syaroslav@ivinco.com
200Syaroslav@ivinco.com/// known searchd commands
210Syaroslav@ivinco.comdefine ( "SEARCHD_COMMAND_SEARCH",	0 );
220Syaroslav@ivinco.comdefine ( "SEARCHD_COMMAND_EXCERPT",	1 );
230Syaroslav@ivinco.comdefine ( "SEARCHD_COMMAND_UPDATE",	2 );
240Syaroslav@ivinco.comdefine ( "SEARCHD_COMMAND_KEYWORDS",3 );
250Syaroslav@ivinco.comdefine ( "SEARCHD_COMMAND_PERSIST",	4 );
260Syaroslav@ivinco.comdefine ( "SEARCHD_COMMAND_STATUS",	5 );
270Syaroslav@ivinco.comdefine ( "SEARCHD_COMMAND_QUERY",	6 );
280Syaroslav@ivinco.com
290Syaroslav@ivinco.com/// current client-side command implementation versions
300Syaroslav@ivinco.comdefine ( "VER_COMMAND_SEARCH",		0x116 );
310Syaroslav@ivinco.comdefine ( "VER_COMMAND_EXCERPT",		0x100 );
320Syaroslav@ivinco.comdefine ( "VER_COMMAND_UPDATE",		0x102 );
330Syaroslav@ivinco.comdefine ( "VER_COMMAND_KEYWORDS",	0x100 );
340Syaroslav@ivinco.comdefine ( "VER_COMMAND_STATUS",		0x100 );
350Syaroslav@ivinco.comdefine ( "VER_COMMAND_QUERY",		0x100 );
360Syaroslav@ivinco.com
370Syaroslav@ivinco.com/// known searchd status codes
380Syaroslav@ivinco.comdefine ( "SEARCHD_OK",				0 );
390Syaroslav@ivinco.comdefine ( "SEARCHD_ERROR",			1 );
400Syaroslav@ivinco.comdefine ( "SEARCHD_RETRY",			2 );
410Syaroslav@ivinco.comdefine ( "SEARCHD_WARNING",			3 );
420Syaroslav@ivinco.com
430Syaroslav@ivinco.com/// known match modes
440Syaroslav@ivinco.comdefine ( "SPH_MATCH_ALL",			0 );
450Syaroslav@ivinco.comdefine ( "SPH_MATCH_ANY",			1 );
460Syaroslav@ivinco.comdefine ( "SPH_MATCH_PHRASE",		2 );
470Syaroslav@ivinco.comdefine ( "SPH_MATCH_BOOLEAN",		3 );
480Syaroslav@ivinco.comdefine ( "SPH_MATCH_EXTENDED",		4 );
490Syaroslav@ivinco.comdefine ( "SPH_MATCH_FULLSCAN",		5 );
500Syaroslav@ivinco.comdefine ( "SPH_MATCH_EXTENDED2",		6 );	// extended engine V2 (TEMPORARY, WILL BE REMOVED)
510Syaroslav@ivinco.com
520Syaroslav@ivinco.com/// known ranking modes (ext2 only)
530Syaroslav@ivinco.comdefine ( "SPH_RANK_PROXIMITY_BM25",	0 );	///< default mode, phrase proximity major factor and BM25 minor one
540Syaroslav@ivinco.comdefine ( "SPH_RANK_BM25",			1 );	///< statistical mode, BM25 ranking only (faster but worse quality)
550Syaroslav@ivinco.comdefine ( "SPH_RANK_NONE",			2 );	///< no ranking, all matches get a weight of 1
560Syaroslav@ivinco.comdefine ( "SPH_RANK_WORDCOUNT",		3 );	///< simple word-count weighting, rank is a weighted sum of per-field keyword occurence counts
570Syaroslav@ivinco.comdefine ( "SPH_RANK_PROXIMITY",		4 );
580Syaroslav@ivinco.comdefine ( "SPH_RANK_MATCHANY",		5 );
590Syaroslav@ivinco.comdefine ( "SPH_RANK_FIELDMASK",		6 );
600Syaroslav@ivinco.com
610Syaroslav@ivinco.com/// known sort modes
620Syaroslav@ivinco.comdefine ( "SPH_SORT_RELEVANCE",		0 );
630Syaroslav@ivinco.comdefine ( "SPH_SORT_ATTR_DESC",		1 );
640Syaroslav@ivinco.comdefine ( "SPH_SORT_ATTR_ASC",		2 );
650Syaroslav@ivinco.comdefine ( "SPH_SORT_TIME_SEGMENTS", 	3 );
660Syaroslav@ivinco.comdefine ( "SPH_SORT_EXTENDED", 		4 );
670Syaroslav@ivinco.comdefine ( "SPH_SORT_EXPR", 			5 );
680Syaroslav@ivinco.com
690Syaroslav@ivinco.com/// known filter types
700Syaroslav@ivinco.comdefine ( "SPH_FILTER_VALUES",		0 );
710Syaroslav@ivinco.comdefine ( "SPH_FILTER_RANGE",		1 );
720Syaroslav@ivinco.comdefine ( "SPH_FILTER_FLOATRANGE",	2 );
730Syaroslav@ivinco.com
740Syaroslav@ivinco.com/// known attribute types
750Syaroslav@ivinco.comdefine ( "SPH_ATTR_INTEGER",		1 );
760Syaroslav@ivinco.comdefine ( "SPH_ATTR_TIMESTAMP",		2 );
770Syaroslav@ivinco.comdefine ( "SPH_ATTR_ORDINAL",		3 );
780Syaroslav@ivinco.comdefine ( "SPH_ATTR_BOOL",			4 );
790Syaroslav@ivinco.comdefine ( "SPH_ATTR_FLOAT",			5 );
800Syaroslav@ivinco.comdefine ( "SPH_ATTR_BIGINT",			6 );
810Syaroslav@ivinco.comdefine ( "SPH_ATTR_MULTI",			0x40000000 );
820Syaroslav@ivinco.com
830Syaroslav@ivinco.com/// known grouping functions
840Syaroslav@ivinco.comdefine ( "SPH_GROUPBY_DAY",			0 );
850Syaroslav@ivinco.comdefine ( "SPH_GROUPBY_WEEK",		1 );
860Syaroslav@ivinco.comdefine ( "SPH_GROUPBY_MONTH",		2 );
870Syaroslav@ivinco.comdefine ( "SPH_GROUPBY_YEAR",		3 );
880Syaroslav@ivinco.comdefine ( "SPH_GROUPBY_ATTR",		4 );
890Syaroslav@ivinco.comdefine ( "SPH_GROUPBY_ATTRPAIR",	5 );
900Syaroslav@ivinco.com
910Syaroslav@ivinco.com// important properties of PHP's integers:
920Syaroslav@ivinco.com//  - always signed (one bit short of PHP_INT_SIZE)
930Syaroslav@ivinco.com//  - conversion from string to int is saturated
940Syaroslav@ivinco.com//  - float is double
950Syaroslav@ivinco.com//  - div converts arguments to floats
960Syaroslav@ivinco.com//  - mod converts arguments to ints
970Syaroslav@ivinco.com
980Syaroslav@ivinco.com// the packing code below works as follows:
990Syaroslav@ivinco.com//  - when we got an int, just pack it
1000Syaroslav@ivinco.com//    if performance is a problem, this is the branch users should aim for
1010Syaroslav@ivinco.com//
1020Syaroslav@ivinco.com//  - otherwise, we got a number in string form
1030Syaroslav@ivinco.com//    this might be due to different reasons, but we assume that this is
1040Syaroslav@ivinco.com//    because it didn't fit into PHP int
1050Syaroslav@ivinco.com//
1060Syaroslav@ivinco.com//  - factor the string into high and low ints for packing
1070Syaroslav@ivinco.com//    - if we have bcmath, then it is used
1080Syaroslav@ivinco.com//    - if we don't, we have to do it manually (this is the fun part)
1090Syaroslav@ivinco.com//
1100Syaroslav@ivinco.com//    - x64 branch does factoring using ints
1110Syaroslav@ivinco.com//    - x32 (ab)uses floats, since we can't fit unsigned 32-bit number into an int
1120Syaroslav@ivinco.com//
1130Syaroslav@ivinco.com// unpacking routines are pretty much the same.
1140Syaroslav@ivinco.com//  - return ints if we can
1150Syaroslav@ivinco.com//  - otherwise format number into a string
1160Syaroslav@ivinco.com
1170Syaroslav@ivinco.com/// pack 64-bit signed
1180Syaroslav@ivinco.comfunction sphPackI64 ( $v )
1190Syaroslav@ivinco.com{
1200Syaroslav@ivinco.com	assert ( is_numeric($v) );
1210Syaroslav@ivinco.com
1220Syaroslav@ivinco.com	// x64
1230Syaroslav@ivinco.com	if ( PHP_INT_SIZE>=8 )
1240Syaroslav@ivinco.com	{
1250Syaroslav@ivinco.com		$v = (int)$v;
1260Syaroslav@ivinco.com		return pack ( "NN", $v>>32, $v&0xFFFFFFFF );
1270Syaroslav@ivinco.com	}
1280Syaroslav@ivinco.com
1290Syaroslav@ivinco.com	// x32, int
1300Syaroslav@ivinco.com	if ( is_int($v) )
1310Syaroslav@ivinco.com		return pack ( "NN", $v < 0 ? -1 : 0, $v );
1320Syaroslav@ivinco.com
1330Syaroslav@ivinco.com	// x32, bcmath
1340Syaroslav@ivinco.com	if ( function_exists("bcmul") )
1350Syaroslav@ivinco.com	{
1360Syaroslav@ivinco.com		if ( bccomp ( $v, 0 ) == -1 )
1370Syaroslav@ivinco.com			$v = bcadd ( "18446744073709551616", $v );
1380Syaroslav@ivinco.com		$h = bcdiv ( $v, "4294967296", 0 );
1390Syaroslav@ivinco.com		$l = bcmod ( $v, "4294967296" );
1400Syaroslav@ivinco.com		return pack ( "NN", (float)$h, (float)$l ); // conversion to float is intentional; int would lose 31st bit
1410Syaroslav@ivinco.com	}
1420Syaroslav@ivinco.com
1430Syaroslav@ivinco.com	// x32, no-bcmath
1440Syaroslav@ivinco.com	$p = max(0, strlen($v) - 13);
1450Syaroslav@ivinco.com	$lo = abs((float)substr($v, $p));
1460Syaroslav@ivinco.com	$hi = abs((float)substr($v, 0, $p));
1470Syaroslav@ivinco.com
1480Syaroslav@ivinco.com	$m = $lo + $hi*1316134912.0; // (10 ^ 13) % (1 << 32) = 1316134912
1490Syaroslav@ivinco.com	$q = floor($m/4294967296.0);
1500Syaroslav@ivinco.com	$l = $m - ($q*4294967296.0);
1510Syaroslav@ivinco.com	$h = $hi*2328.0 + $q; // (10 ^ 13) / (1 << 32) = 2328
1520Syaroslav@ivinco.com
1530Syaroslav@ivinco.com	if ( $v<0 )
1540Syaroslav@ivinco.com	{
1550Syaroslav@ivinco.com		if ( $l==0 )
1560Syaroslav@ivinco.com			$h = 4294967296.0 - $h;
1570Syaroslav@ivinco.com		else
1580Syaroslav@ivinco.com		{
1590Syaroslav@ivinco.com			$h = 4294967295.0 - $h;
1600Syaroslav@ivinco.com			$l = 4294967296.0 - $l;
1610Syaroslav@ivinco.com		}
1620Syaroslav@ivinco.com	}
1630Syaroslav@ivinco.com	return pack ( "NN", $h, $l );
1640Syaroslav@ivinco.com}
1650Syaroslav@ivinco.com
1660Syaroslav@ivinco.com/// pack 64-bit unsigned
1670Syaroslav@ivinco.comfunction sphPackU64 ( $v )
1680Syaroslav@ivinco.com{
1690Syaroslav@ivinco.com	assert ( is_numeric($v) );
1700Syaroslav@ivinco.com
1710Syaroslav@ivinco.com	// x64
1720Syaroslav@ivinco.com	if ( PHP_INT_SIZE>=8 )
1730Syaroslav@ivinco.com	{
1740Syaroslav@ivinco.com		assert ( $v>=0 );
1750Syaroslav@ivinco.com
1760Syaroslav@ivinco.com		// x64, int
1770Syaroslav@ivinco.com		if ( is_int($v) )
1780Syaroslav@ivinco.com			return pack ( "NN", $v>>32, $v&0xFFFFFFFF );
1790Syaroslav@ivinco.com
1800Syaroslav@ivinco.com		// x64, bcmath
1810Syaroslav@ivinco.com		if ( function_exists("bcmul") )
1820Syaroslav@ivinco.com		{
1830Syaroslav@ivinco.com			$h = bcdiv ( $v, 4294967296, 0 );
1840Syaroslav@ivinco.com			$l = bcmod ( $v, 4294967296 );
1850Syaroslav@ivinco.com			return pack ( "NN", $h, $l );
1860Syaroslav@ivinco.com		}
1870Syaroslav@ivinco.com
1880Syaroslav@ivinco.com		// x64, no-bcmath
1890Syaroslav@ivinco.com		$p = max ( 0, strlen($v) - 13 );
1900Syaroslav@ivinco.com		$lo = (int)substr ( $v, $p );
1910Syaroslav@ivinco.com		$hi = (int)substr ( $v, 0, $p );
1920Syaroslav@ivinco.com
1930Syaroslav@ivinco.com		$m = $lo + $hi*1316134912;
1940Syaroslav@ivinco.com		$l = $m % 4294967296;
1950Syaroslav@ivinco.com		$h = $hi*2328 + (int)($m/4294967296);
1960Syaroslav@ivinco.com
1970Syaroslav@ivinco.com		return pack ( "NN", $h, $l );
1980Syaroslav@ivinco.com	}
1990Syaroslav@ivinco.com
2000Syaroslav@ivinco.com	// x32, int
2010Syaroslav@ivinco.com	if ( is_int($v) )
2020Syaroslav@ivinco.com		return pack ( "NN", 0, $v );
2030Syaroslav@ivinco.com
2040Syaroslav@ivinco.com	// x32, bcmath
2050Syaroslav@ivinco.com	if ( function_exists("bcmul") )
2060Syaroslav@ivinco.com	{
2070Syaroslav@ivinco.com		$h = bcdiv ( $v, "4294967296", 0 );
2080Syaroslav@ivinco.com		$l = bcmod ( $v, "4294967296" );
2090Syaroslav@ivinco.com		return pack ( "NN", (float)$h, (float)$l ); // conversion to float is intentional; int would lose 31st bit
2100Syaroslav@ivinco.com	}
2110Syaroslav@ivinco.com
2120Syaroslav@ivinco.com	// x32, no-bcmath
2130Syaroslav@ivinco.com	$p = max(0, strlen($v) - 13);
2140Syaroslav@ivinco.com	$lo = (float)substr($v, $p);
2150Syaroslav@ivinco.com	$hi = (float)substr($v, 0, $p);
2160Syaroslav@ivinco.com
2170Syaroslav@ivinco.com	$m = $lo + $hi*1316134912.0;
2180Syaroslav@ivinco.com	$q = floor($m / 4294967296.0);
2190Syaroslav@ivinco.com	$l = $m - ($q * 4294967296.0);
2200Syaroslav@ivinco.com	$h = $hi*2328.0 + $q;
2210Syaroslav@ivinco.com
2220Syaroslav@ivinco.com	return pack ( "NN", $h, $l );
2230Syaroslav@ivinco.com}
2240Syaroslav@ivinco.com
2250Syaroslav@ivinco.com// unpack 64-bit unsigned
2260Syaroslav@ivinco.comfunction sphUnpackU64 ( $v )
2270Syaroslav@ivinco.com{
2280Syaroslav@ivinco.com	list ( $hi, $lo ) = array_values ( unpack ( "N*N*", $v ) );
2290Syaroslav@ivinco.com
2300Syaroslav@ivinco.com	if ( PHP_INT_SIZE>=8 )
2310Syaroslav@ivinco.com	{
2320Syaroslav@ivinco.com		if ( $hi<0 ) $hi += (1<<32); // because php 5.2.2 to 5.2.5 is totally fucked up again
2330Syaroslav@ivinco.com		if ( $lo<0 ) $lo += (1<<32);
2340Syaroslav@ivinco.com
2350Syaroslav@ivinco.com		// x64, int
2360Syaroslav@ivinco.com		if ( $hi<=2147483647 )
2370Syaroslav@ivinco.com			return ($hi<<32) + $lo;
2380Syaroslav@ivinco.com
2390Syaroslav@ivinco.com		// x64, bcmath
2400Syaroslav@ivinco.com		if ( function_exists("bcmul") )
2410Syaroslav@ivinco.com			return bcadd ( $lo, bcmul ( $hi, "4294967296" ) );
2420Syaroslav@ivinco.com
2430Syaroslav@ivinco.com		// x64, no-bcmath
2440Syaroslav@ivinco.com		$C = 100000;
2450Syaroslav@ivinco.com		$h = ((int)($hi / $C) << 32) + (int)($lo / $C);
2460Syaroslav@ivinco.com		$l = (($hi % $C) << 32) + ($lo % $C);
2470Syaroslav@ivinco.com		if ( $l>$C )
2480Syaroslav@ivinco.com		{
2490Syaroslav@ivinco.com			$h += (int)($l / $C);
2500Syaroslav@ivinco.com			$l  = $l % $C;
2510Syaroslav@ivinco.com		}
2520Syaroslav@ivinco.com
2530Syaroslav@ivinco.com		if ( $h==0 )
2540Syaroslav@ivinco.com			return $l;
2550Syaroslav@ivinco.com		return sprintf ( "%d%05d", $h, $l );
2560Syaroslav@ivinco.com	}
2570Syaroslav@ivinco.com
2580Syaroslav@ivinco.com	// x32, int
2590Syaroslav@ivinco.com	if ( $hi==0 )
2600Syaroslav@ivinco.com	{
2610Syaroslav@ivinco.com		if ( $lo>0 )
2620Syaroslav@ivinco.com			return $lo;
2630Syaroslav@ivinco.com		return sprintf ( "%u", $lo );
2640Syaroslav@ivinco.com	}
2650Syaroslav@ivinco.com
2660Syaroslav@ivinco.com	$hi = sprintf ( "%u", $hi );
2670Syaroslav@ivinco.com	$lo = sprintf ( "%u", $lo );
2680Syaroslav@ivinco.com
2690Syaroslav@ivinco.com	// x32, bcmath
2700Syaroslav@ivinco.com	if ( function_exists("bcmul") )
2710Syaroslav@ivinco.com		return bcadd ( $lo, bcmul ( $hi, "4294967296" ) );
2720Syaroslav@ivinco.com
2730Syaroslav@ivinco.com	// x32, no-bcmath
2740Syaroslav@ivinco.com	$hi = (float)$hi;
2750Syaroslav@ivinco.com	$lo = (float)$lo;
2760Syaroslav@ivinco.com
2770Syaroslav@ivinco.com	$q = floor($hi/10000000.0);
2780Syaroslav@ivinco.com	$r = $hi - $q*10000000.0;
2790Syaroslav@ivinco.com	$m = $lo + $r*4967296.0;
2800Syaroslav@ivinco.com	$mq = floor($m/10000000.0);
2810Syaroslav@ivinco.com	$l = $m - $mq*10000000.0;
2820Syaroslav@ivinco.com	$h = $q*4294967296.0 + $r*429.0 + $mq;
2830Syaroslav@ivinco.com
2840Syaroslav@ivinco.com	$h = sprintf ( "%.0f", $h );
2850Syaroslav@ivinco.com	$l = sprintf ( "%07.0f", $l );
2860Syaroslav@ivinco.com	if ( $h=="0" )
2870Syaroslav@ivinco.com		return sprintf( "%.0f", (float)$l );
2880Syaroslav@ivinco.com	return $h . $l;
2890Syaroslav@ivinco.com}
2900Syaroslav@ivinco.com
2910Syaroslav@ivinco.com// unpack 64-bit signed
2920Syaroslav@ivinco.comfunction sphUnpackI64 ( $v )
2930Syaroslav@ivinco.com{
2940Syaroslav@ivinco.com	list ( $hi, $lo ) = array_values ( unpack ( "N*N*", $v ) );
2950Syaroslav@ivinco.com
2960Syaroslav@ivinco.com	// x64
2970Syaroslav@ivinco.com	if ( PHP_INT_SIZE>=8 )
2980Syaroslav@ivinco.com	{
2990Syaroslav@ivinco.com		if ( $hi<0 ) $hi += (1<<32); // because php 5.2.2 to 5.2.5 is totally fucked up again
3000Syaroslav@ivinco.com		if ( $lo<0 ) $lo += (1<<32);
3010Syaroslav@ivinco.com
3020Syaroslav@ivinco.com		return ($hi<<32) + $lo;
3030Syaroslav@ivinco.com	}
3040Syaroslav@ivinco.com
3050Syaroslav@ivinco.com	// x32, int
3060Syaroslav@ivinco.com	if ( $hi==0 )
3070Syaroslav@ivinco.com	{
3080Syaroslav@ivinco.com		if ( $lo>0 )
3090Syaroslav@ivinco.com			return $lo;
3100Syaroslav@ivinco.com		return sprintf ( "%u", $lo );
3110Syaroslav@ivinco.com	}
3120Syaroslav@ivinco.com	// x32, int
3130Syaroslav@ivinco.com	elseif ( $hi==-1 )
3140Syaroslav@ivinco.com	{
3150Syaroslav@ivinco.com		if ( $lo<0 )
3160Syaroslav@ivinco.com			return $lo;
3170Syaroslav@ivinco.com		return sprintf ( "%.0f", $lo - 4294967296.0 );
3180Syaroslav@ivinco.com	}
3190Syaroslav@ivinco.com
3200Syaroslav@ivinco.com	$neg = "";
3210Syaroslav@ivinco.com	$c = 0;
3220Syaroslav@ivinco.com	if ( $hi<0 )
3230Syaroslav@ivinco.com	{
3240Syaroslav@ivinco.com		$hi = ~$hi;
3250Syaroslav@ivinco.com		$lo = ~$lo;
3260Syaroslav@ivinco.com		$c = 1;
3270Syaroslav@ivinco.com		$neg = "-";
3280Syaroslav@ivinco.com	}
3290Syaroslav@ivinco.com
3300Syaroslav@ivinco.com	$hi = sprintf ( "%u", $hi );
3310Syaroslav@ivinco.com	$lo = sprintf ( "%u", $lo );
3320Syaroslav@ivinco.com
3330Syaroslav@ivinco.com	// x32, bcmath
3340Syaroslav@ivinco.com	if ( function_exists("bcmul") )
3350Syaroslav@ivinco.com		return $neg . bcadd ( bcadd ( $lo, bcmul ( $hi, "4294967296" ) ), $c );
3360Syaroslav@ivinco.com
3370Syaroslav@ivinco.com	// x32, no-bcmath
3380Syaroslav@ivinco.com	$hi = (float)$hi;
3390Syaroslav@ivinco.com	$lo = (float)$lo;
3400Syaroslav@ivinco.com
3410Syaroslav@ivinco.com	$q = floor($hi/10000000.0);
3420Syaroslav@ivinco.com	$r = $hi - $q*10000000.0;
3430Syaroslav@ivinco.com	$m = $lo + $r*4967296.0;
3440Syaroslav@ivinco.com	$mq = floor($m/10000000.0);
3450Syaroslav@ivinco.com	$l = $m - $mq*10000000.0 + $c;
3460Syaroslav@ivinco.com	$h = $q*4294967296.0 + $r*429.0 + $mq;
3470Syaroslav@ivinco.com	if ( $l==10000000 )
3480Syaroslav@ivinco.com	{
3490Syaroslav@ivinco.com		$l = 0;
3500Syaroslav@ivinco.com		$h += 1;
3510Syaroslav@ivinco.com	}
3520Syaroslav@ivinco.com
3530Syaroslav@ivinco.com	$h = sprintf ( "%.0f", $h );
3540Syaroslav@ivinco.com	$l = sprintf ( "%07.0f", $l );
3550Syaroslav@ivinco.com	if ( $h=="0" )
3560Syaroslav@ivinco.com		return $neg . sprintf( "%.0f", (float)$l );
3570Syaroslav@ivinco.com	return $neg . $h . $l;
3580Syaroslav@ivinco.com}
3590Syaroslav@ivinco.com
3600Syaroslav@ivinco.com
3610Syaroslav@ivinco.comfunction sphFixUint ( $value )
3620Syaroslav@ivinco.com{
3630Syaroslav@ivinco.com	if ( PHP_INT_SIZE>=8 )
3640Syaroslav@ivinco.com	{
3650Syaroslav@ivinco.com		// x64 route, workaround broken unpack() in 5.2.2+
3660Syaroslav@ivinco.com		if ( $value<0 ) $value += (1<<32);
3670Syaroslav@ivinco.com		return $value;
3680Syaroslav@ivinco.com	}
3690Syaroslav@ivinco.com	else
3700Syaroslav@ivinco.com	{
3710Syaroslav@ivinco.com		// x32 route, workaround php signed/unsigned braindamage
3720Syaroslav@ivinco.com		return sprintf ( "%u", $value );
3730Syaroslav@ivinco.com	}
3740Syaroslav@ivinco.com}
3750Syaroslav@ivinco.com
376*133Sandreyif (!class_exists('SphinxClient')) {
3770Syaroslav@ivinco.com/// sphinx searchd client class
3780Syaroslav@ivinco.comclass SphinxClient
3790Syaroslav@ivinco.com{
3800Syaroslav@ivinco.com	var $_host;			///< searchd host (default is "localhost")
3810Syaroslav@ivinco.com	var $_port;			///< searchd port (default is 9312)
3820Syaroslav@ivinco.com	var $_offset;		///< how many records to seek from result-set start (default is 0)
3830Syaroslav@ivinco.com	var $_limit;		///< how many records to return from result-set starting at offset (default is 20)
3840Syaroslav@ivinco.com	var $_mode;			///< query matching mode (default is SPH_MATCH_ALL)
3850Syaroslav@ivinco.com	var $_weights;		///< per-field weights (default is 1 for all fields)
3860Syaroslav@ivinco.com	var $_sort;			///< match sorting mode (default is SPH_SORT_RELEVANCE)
3870Syaroslav@ivinco.com	var $_sortby;		///< attribute to sort by (defualt is "")
3880Syaroslav@ivinco.com	var $_min_id;		///< min ID to match (default is 0, which means no limit)
3890Syaroslav@ivinco.com	var $_max_id;		///< max ID to match (default is 0, which means no limit)
3900Syaroslav@ivinco.com	var $_filters;		///< search filters
3910Syaroslav@ivinco.com	var $_groupby;		///< group-by attribute name
3920Syaroslav@ivinco.com	var $_groupfunc;	///< group-by function (to pre-process group-by attribute value with)
3930Syaroslav@ivinco.com	var $_groupsort;	///< group-by sorting clause (to sort groups in result set with)
3940Syaroslav@ivinco.com	var $_groupdistinct;///< group-by count-distinct attribute
3950Syaroslav@ivinco.com	var $_maxmatches;	///< max matches to retrieve
3960Syaroslav@ivinco.com	var $_cutoff;		///< cutoff to stop searching at (default is 0)
3970Syaroslav@ivinco.com	var $_retrycount;	///< distributed retries count
3980Syaroslav@ivinco.com	var $_retrydelay;	///< distributed retries delay
3990Syaroslav@ivinco.com	var $_anchor;		///< geographical anchor point
4000Syaroslav@ivinco.com	var $_indexweights;	///< per-index weights
4010Syaroslav@ivinco.com	var $_ranker;		///< ranking mode (default is SPH_RANK_PROXIMITY_BM25)
4020Syaroslav@ivinco.com	var $_maxquerytime;	///< max query time, milliseconds (default is 0, do not limit)
4030Syaroslav@ivinco.com	var $_fieldweights;	///< per-field-name weights
4040Syaroslav@ivinco.com	var $_overrides;	///< per-query attribute values overrides
4050Syaroslav@ivinco.com	var $_select;		///< select-list (attributes or expressions, with optional aliases)
4060Syaroslav@ivinco.com
4070Syaroslav@ivinco.com	var $_error;		///< last error message
4080Syaroslav@ivinco.com	var $_warning;		///< last warning message
4090Syaroslav@ivinco.com	var $_connerror;		///< connection error vs remote error flag
4100Syaroslav@ivinco.com
4110Syaroslav@ivinco.com	var $_reqs;			///< requests array for multi-query
4120Syaroslav@ivinco.com	var $_mbenc;		///< stored mbstring encoding
4130Syaroslav@ivinco.com	var $_arrayresult;	///< whether $result["matches"] should be a hash or an array
4140Syaroslav@ivinco.com	var $_timeout;		///< connect timeout
4150Syaroslav@ivinco.com
4160Syaroslav@ivinco.com	/////////////////////////////////////////////////////////////////////////////
4170Syaroslav@ivinco.com	// common stuff
4180Syaroslav@ivinco.com	/////////////////////////////////////////////////////////////////////////////
4190Syaroslav@ivinco.com
4200Syaroslav@ivinco.com	/// create a new client object and fill defaults
4210Syaroslav@ivinco.com	function SphinxClient ()
4220Syaroslav@ivinco.com	{
4230Syaroslav@ivinco.com		// per-client-object settings
4240Syaroslav@ivinco.com		$this->_host		= "localhost";
4250Syaroslav@ivinco.com		$this->_port		= 9312;
4260Syaroslav@ivinco.com		$this->_path		= false;
4270Syaroslav@ivinco.com		$this->_socket		= false;
4280Syaroslav@ivinco.com
4290Syaroslav@ivinco.com		// per-query settings
4300Syaroslav@ivinco.com		$this->_offset		= 0;
4310Syaroslav@ivinco.com		$this->_limit		= 20;
4320Syaroslav@ivinco.com		$this->_mode		= SPH_MATCH_ALL;
4330Syaroslav@ivinco.com		$this->_weights		= array ();
4340Syaroslav@ivinco.com		$this->_sort		= SPH_SORT_RELEVANCE;
4350Syaroslav@ivinco.com		$this->_sortby		= "";
4360Syaroslav@ivinco.com		$this->_min_id		= 0;
4370Syaroslav@ivinco.com		$this->_max_id		= 0;
4380Syaroslav@ivinco.com		$this->_filters		= array ();
4390Syaroslav@ivinco.com		$this->_groupby		= "";
4400Syaroslav@ivinco.com		$this->_groupfunc	= SPH_GROUPBY_DAY;
4410Syaroslav@ivinco.com		$this->_groupsort	= "@group desc";
4420Syaroslav@ivinco.com		$this->_groupdistinct= "";
4430Syaroslav@ivinco.com		$this->_maxmatches	= 1000;
4440Syaroslav@ivinco.com		$this->_cutoff		= 0;
4450Syaroslav@ivinco.com		$this->_retrycount	= 0;
4460Syaroslav@ivinco.com		$this->_retrydelay	= 0;
4470Syaroslav@ivinco.com		$this->_anchor		= array ();
4480Syaroslav@ivinco.com		$this->_indexweights= array ();
4490Syaroslav@ivinco.com		$this->_ranker		= SPH_RANK_PROXIMITY_BM25;
4500Syaroslav@ivinco.com		$this->_maxquerytime= 0;
4510Syaroslav@ivinco.com		$this->_fieldweights= array();
4520Syaroslav@ivinco.com		$this->_overrides 	= array();
4530Syaroslav@ivinco.com		$this->_select		= "*";
4540Syaroslav@ivinco.com
4550Syaroslav@ivinco.com		$this->_error		= ""; // per-reply fields (for single-query case)
4560Syaroslav@ivinco.com		$this->_warning		= "";
4570Syaroslav@ivinco.com		$this->_connerror	= false;
4580Syaroslav@ivinco.com
4590Syaroslav@ivinco.com		$this->_reqs		= array ();	// requests storage (for multi-query case)
4600Syaroslav@ivinco.com		$this->_mbenc		= "";
4610Syaroslav@ivinco.com		$this->_arrayresult	= false;
4620Syaroslav@ivinco.com		$this->_timeout		= 0;
4630Syaroslav@ivinco.com	}
4640Syaroslav@ivinco.com
4650Syaroslav@ivinco.com	function __destruct()
4660Syaroslav@ivinco.com	{
4670Syaroslav@ivinco.com		if ( $this->_socket !== false )
4680Syaroslav@ivinco.com			fclose ( $this->_socket );
4690Syaroslav@ivinco.com	}
4700Syaroslav@ivinco.com
4710Syaroslav@ivinco.com	/// get last error message (string)
4720Syaroslav@ivinco.com	function GetLastError ()
4730Syaroslav@ivinco.com	{
4740Syaroslav@ivinco.com		return $this->_error;
4750Syaroslav@ivinco.com	}
4760Syaroslav@ivinco.com
4770Syaroslav@ivinco.com	/// get last warning message (string)
4780Syaroslav@ivinco.com	function GetLastWarning ()
4790Syaroslav@ivinco.com	{
4800Syaroslav@ivinco.com		return $this->_warning;
4810Syaroslav@ivinco.com	}
4820Syaroslav@ivinco.com
4830Syaroslav@ivinco.com	/// get last error flag (to tell network connection errors from searchd errors or broken responses)
4840Syaroslav@ivinco.com	function IsConnectError()
4850Syaroslav@ivinco.com	{
4860Syaroslav@ivinco.com		return $this->_connerror;
4870Syaroslav@ivinco.com	}
4880Syaroslav@ivinco.com
4890Syaroslav@ivinco.com	/// set searchd host name (string) and port (integer)
4900Syaroslav@ivinco.com	function SetServer ( $host, $port = 0 )
4910Syaroslav@ivinco.com	{
4920Syaroslav@ivinco.com		assert ( is_string($host) );
4930Syaroslav@ivinco.com		if ( $host[0] == '/')
4940Syaroslav@ivinco.com		{
4950Syaroslav@ivinco.com			$this->_path = 'unix://' . $host;
4960Syaroslav@ivinco.com			return;
4970Syaroslav@ivinco.com		}
4980Syaroslav@ivinco.com		if ( substr ( $host, 0, 7 )=="unix://" )
4990Syaroslav@ivinco.com		{
5000Syaroslav@ivinco.com			$this->_path = $host;
5010Syaroslav@ivinco.com			return;
5020Syaroslav@ivinco.com		}
5030Syaroslav@ivinco.com
5040Syaroslav@ivinco.com		assert ( is_int($port) );
5050Syaroslav@ivinco.com		$this->_host = $host;
5060Syaroslav@ivinco.com		$this->_port = $port;
5070Syaroslav@ivinco.com		$this->_path = '';
5080Syaroslav@ivinco.com
5090Syaroslav@ivinco.com	}
5100Syaroslav@ivinco.com
5110Syaroslav@ivinco.com	/// set server connection timeout (0 to remove)
5120Syaroslav@ivinco.com	function SetConnectTimeout ( $timeout )
5130Syaroslav@ivinco.com	{
5140Syaroslav@ivinco.com		assert ( is_numeric($timeout) );
5150Syaroslav@ivinco.com		$this->_timeout = $timeout;
5160Syaroslav@ivinco.com	}
5170Syaroslav@ivinco.com
5180Syaroslav@ivinco.com
5190Syaroslav@ivinco.com	function _Send ( $handle, $data, $length )
5200Syaroslav@ivinco.com	{
5210Syaroslav@ivinco.com		if ( feof($handle) || fwrite ( $handle, $data, $length ) !== $length )
5220Syaroslav@ivinco.com		{
5230Syaroslav@ivinco.com			$this->_error = 'connection unexpectedly closed (timed out?)';
5240Syaroslav@ivinco.com			$this->_connerror = true;
5250Syaroslav@ivinco.com			return false;
5260Syaroslav@ivinco.com		}
5270Syaroslav@ivinco.com		return true;
5280Syaroslav@ivinco.com	}
5290Syaroslav@ivinco.com
5300Syaroslav@ivinco.com	/////////////////////////////////////////////////////////////////////////////
5310Syaroslav@ivinco.com
5320Syaroslav@ivinco.com	/// enter mbstring workaround mode
5330Syaroslav@ivinco.com	function _MBPush ()
5340Syaroslav@ivinco.com	{
5350Syaroslav@ivinco.com		$this->_mbenc = "";
5360Syaroslav@ivinco.com		if ( ini_get ( "mbstring.func_overload" ) & 2 )
5370Syaroslav@ivinco.com		{
5380Syaroslav@ivinco.com			$this->_mbenc = mb_internal_encoding();
5390Syaroslav@ivinco.com			mb_internal_encoding ( "latin1" );
5400Syaroslav@ivinco.com		}
5410Syaroslav@ivinco.com    }
5420Syaroslav@ivinco.com
5430Syaroslav@ivinco.com	/// leave mbstring workaround mode
5440Syaroslav@ivinco.com	function _MBPop ()
5450Syaroslav@ivinco.com	{
5460Syaroslav@ivinco.com		if ( $this->_mbenc )
5470Syaroslav@ivinco.com			mb_internal_encoding ( $this->_mbenc );
5480Syaroslav@ivinco.com	}
5490Syaroslav@ivinco.com
5500Syaroslav@ivinco.com	/// connect to searchd server
5510Syaroslav@ivinco.com	function _Connect ()
5520Syaroslav@ivinco.com	{
5530Syaroslav@ivinco.com		if ( $this->_socket!==false )
5540Syaroslav@ivinco.com		{
5550Syaroslav@ivinco.com			// we are in persistent connection mode, so we have a socket
5560Syaroslav@ivinco.com			// however, need to check whether it's still alive
5570Syaroslav@ivinco.com			if ( !@feof ( $this->_socket ) )
5580Syaroslav@ivinco.com				return $this->_socket;
5590Syaroslav@ivinco.com
5600Syaroslav@ivinco.com			// force reopen
5610Syaroslav@ivinco.com			$this->_socket = false;
5620Syaroslav@ivinco.com		}
5630Syaroslav@ivinco.com
5640Syaroslav@ivinco.com		$errno = 0;
5650Syaroslav@ivinco.com		$errstr = "";
5660Syaroslav@ivinco.com		$this->_connerror = false;
5670Syaroslav@ivinco.com
5680Syaroslav@ivinco.com		if ( $this->_path )
5690Syaroslav@ivinco.com		{
5700Syaroslav@ivinco.com			$host = $this->_path;
5710Syaroslav@ivinco.com			$port = 0;
5720Syaroslav@ivinco.com		}
5730Syaroslav@ivinco.com		else
5740Syaroslav@ivinco.com		{
5750Syaroslav@ivinco.com			$host = $this->_host;
5760Syaroslav@ivinco.com			$port = $this->_port;
5770Syaroslav@ivinco.com		}
5780Syaroslav@ivinco.com
5790Syaroslav@ivinco.com		if ( $this->_timeout<=0 )
5800Syaroslav@ivinco.com			$fp = @fsockopen ( $host, $port, $errno, $errstr );
5810Syaroslav@ivinco.com		else
5820Syaroslav@ivinco.com			$fp = @fsockopen ( $host, $port, $errno, $errstr, $this->_timeout );
5830Syaroslav@ivinco.com
5840Syaroslav@ivinco.com		if ( !$fp )
5850Syaroslav@ivinco.com		{
5860Syaroslav@ivinco.com			if ( $this->_path )
5870Syaroslav@ivinco.com				$location = $this->_path;
5880Syaroslav@ivinco.com			else
5890Syaroslav@ivinco.com				$location = "{$this->_host}:{$this->_port}";
5900Syaroslav@ivinco.com
5910Syaroslav@ivinco.com			$errstr = trim ( $errstr );
5920Syaroslav@ivinco.com			$this->_error = "connection to $location failed (errno=$errno, msg=$errstr)";
5930Syaroslav@ivinco.com			$this->_connerror = true;
5940Syaroslav@ivinco.com			return false;
5950Syaroslav@ivinco.com		}
5960Syaroslav@ivinco.com
5970Syaroslav@ivinco.com		// send my version
5980Syaroslav@ivinco.com		// this is a subtle part. we must do it before (!) reading back from searchd.
5990Syaroslav@ivinco.com		// because otherwise under some conditions (reported on FreeBSD for instance)
6000Syaroslav@ivinco.com		// TCP stack could throttle write-write-read pattern because of Nagle.
6010Syaroslav@ivinco.com		if ( !$this->_Send ( $fp, pack ( "N", 1 ), 4 ) )
6020Syaroslav@ivinco.com		{
6030Syaroslav@ivinco.com			fclose ( $fp );
6040Syaroslav@ivinco.com			$this->_error = "failed to send client protocol version";
6050Syaroslav@ivinco.com			return false;
6060Syaroslav@ivinco.com		}
6070Syaroslav@ivinco.com
6080Syaroslav@ivinco.com		// check version
6090Syaroslav@ivinco.com		list(,$v) = unpack ( "N*", fread ( $fp, 4 ) );
6100Syaroslav@ivinco.com		$v = (int)$v;
6110Syaroslav@ivinco.com		if ( $v<1 )
6120Syaroslav@ivinco.com		{
6130Syaroslav@ivinco.com			fclose ( $fp );
6140Syaroslav@ivinco.com			$this->_error = "expected searchd protocol version 1+, got version '$v'";
6150Syaroslav@ivinco.com			return false;
6160Syaroslav@ivinco.com		}
6170Syaroslav@ivinco.com
6180Syaroslav@ivinco.com		return $fp;
6190Syaroslav@ivinco.com	}
6200Syaroslav@ivinco.com
6210Syaroslav@ivinco.com	/// get and check response packet from searchd server
6220Syaroslav@ivinco.com	function _GetResponse ( $fp, $client_ver )
6230Syaroslav@ivinco.com	{
6240Syaroslav@ivinco.com		$response = "";
6250Syaroslav@ivinco.com		$len = 0;
6260Syaroslav@ivinco.com
6270Syaroslav@ivinco.com		$header = fread ( $fp, 8 );
6280Syaroslav@ivinco.com		if ( strlen($header)==8 )
6290Syaroslav@ivinco.com		{
6300Syaroslav@ivinco.com			list ( $status, $ver, $len ) = array_values ( unpack ( "n2a/Nb", $header ) );
6310Syaroslav@ivinco.com			$left = $len;
6320Syaroslav@ivinco.com			while ( $left>0 && !feof($fp) )
6330Syaroslav@ivinco.com			{
6340Syaroslav@ivinco.com				$chunk = fread ( $fp, $left );
6350Syaroslav@ivinco.com				if ( $chunk )
6360Syaroslav@ivinco.com				{
6370Syaroslav@ivinco.com					$response .= $chunk;
6380Syaroslav@ivinco.com					$left -= strlen($chunk);
6390Syaroslav@ivinco.com				}
6400Syaroslav@ivinco.com			}
6410Syaroslav@ivinco.com		}
6420Syaroslav@ivinco.com		if ( $this->_socket === false )
6430Syaroslav@ivinco.com			fclose ( $fp );
6440Syaroslav@ivinco.com
6450Syaroslav@ivinco.com		// check response
6460Syaroslav@ivinco.com		$read = strlen ( $response );
6470Syaroslav@ivinco.com		if ( !$response || $read!=$len )
6480Syaroslav@ivinco.com		{
6490Syaroslav@ivinco.com			$this->_error = $len
6500Syaroslav@ivinco.com				? "failed to read searchd response (status=$status, ver=$ver, len=$len, read=$read)"
6510Syaroslav@ivinco.com				: "received zero-sized searchd response";
6520Syaroslav@ivinco.com			return false;
6530Syaroslav@ivinco.com		}
6540Syaroslav@ivinco.com
6550Syaroslav@ivinco.com		// check status
6560Syaroslav@ivinco.com		if ( $status==SEARCHD_WARNING )
6570Syaroslav@ivinco.com		{
6580Syaroslav@ivinco.com			list(,$wlen) = unpack ( "N*", substr ( $response, 0, 4 ) );
6590Syaroslav@ivinco.com			$this->_warning = substr ( $response, 4, $wlen );
6600Syaroslav@ivinco.com			return substr ( $response, 4+$wlen );
6610Syaroslav@ivinco.com		}
6620Syaroslav@ivinco.com		if ( $status==SEARCHD_ERROR )
6630Syaroslav@ivinco.com		{
6640Syaroslav@ivinco.com			$this->_error = "searchd error: " . substr ( $response, 4 );
6650Syaroslav@ivinco.com			return false;
6660Syaroslav@ivinco.com		}
6670Syaroslav@ivinco.com		if ( $status==SEARCHD_RETRY )
6680Syaroslav@ivinco.com		{
6690Syaroslav@ivinco.com			$this->_error = "temporary searchd error: " . substr ( $response, 4 );
6700Syaroslav@ivinco.com			return false;
6710Syaroslav@ivinco.com		}
6720Syaroslav@ivinco.com		if ( $status!=SEARCHD_OK )
6730Syaroslav@ivinco.com		{
6740Syaroslav@ivinco.com			$this->_error = "unknown status code '$status'";
6750Syaroslav@ivinco.com			return false;
6760Syaroslav@ivinco.com		}
6770Syaroslav@ivinco.com
6780Syaroslav@ivinco.com		// check version
6790Syaroslav@ivinco.com		if ( $ver<$client_ver )
6800Syaroslav@ivinco.com		{
6810Syaroslav@ivinco.com			$this->_warning = sprintf ( "searchd command v.%d.%d older than client's v.%d.%d, some options might not work",
6820Syaroslav@ivinco.com				$ver>>8, $ver&0xff, $client_ver>>8, $client_ver&0xff );
6830Syaroslav@ivinco.com		}
6840Syaroslav@ivinco.com
6850Syaroslav@ivinco.com		return $response;
6860Syaroslav@ivinco.com	}
6870Syaroslav@ivinco.com
6880Syaroslav@ivinco.com	/////////////////////////////////////////////////////////////////////////////
6890Syaroslav@ivinco.com	// searching
6900Syaroslav@ivinco.com	/////////////////////////////////////////////////////////////////////////////
6910Syaroslav@ivinco.com
6920Syaroslav@ivinco.com	/// set offset and count into result set,
6930Syaroslav@ivinco.com	/// and optionally set max-matches and cutoff limits
6940Syaroslav@ivinco.com	function SetLimits ( $offset, $limit, $max=0, $cutoff=0 )
6950Syaroslav@ivinco.com	{
6960Syaroslav@ivinco.com		assert ( is_int($offset) );
6970Syaroslav@ivinco.com		assert ( is_int($limit) );
6980Syaroslav@ivinco.com		assert ( $offset>=0 );
6990Syaroslav@ivinco.com		assert ( $limit>0 );
7000Syaroslav@ivinco.com		assert ( $max>=0 );
7010Syaroslav@ivinco.com		$this->_offset = $offset;
7020Syaroslav@ivinco.com		$this->_limit = $limit;
7030Syaroslav@ivinco.com		if ( $max>0 )
7040Syaroslav@ivinco.com			$this->_maxmatches = $max;
7050Syaroslav@ivinco.com		if ( $cutoff>0 )
7060Syaroslav@ivinco.com			$this->_cutoff = $cutoff;
7070Syaroslav@ivinco.com	}
7080Syaroslav@ivinco.com
7090Syaroslav@ivinco.com	/// set maximum query time, in milliseconds, per-index
7100Syaroslav@ivinco.com	/// integer, 0 means "do not limit"
7110Syaroslav@ivinco.com	function SetMaxQueryTime ( $max )
7120Syaroslav@ivinco.com	{
7130Syaroslav@ivinco.com		assert ( is_int($max) );
7140Syaroslav@ivinco.com		assert ( $max>=0 );
7150Syaroslav@ivinco.com		$this->_maxquerytime = $max;
7160Syaroslav@ivinco.com	}
7170Syaroslav@ivinco.com
7180Syaroslav@ivinco.com	/// set matching mode
7190Syaroslav@ivinco.com	function SetMatchMode ( $mode )
7200Syaroslav@ivinco.com	{
7210Syaroslav@ivinco.com		assert ( $mode==SPH_MATCH_ALL
7220Syaroslav@ivinco.com			|| $mode==SPH_MATCH_ANY
7230Syaroslav@ivinco.com			|| $mode==SPH_MATCH_PHRASE
7240Syaroslav@ivinco.com			|| $mode==SPH_MATCH_BOOLEAN
7250Syaroslav@ivinco.com			|| $mode==SPH_MATCH_EXTENDED
7260Syaroslav@ivinco.com			|| $mode==SPH_MATCH_FULLSCAN
7270Syaroslav@ivinco.com			|| $mode==SPH_MATCH_EXTENDED2 );
7280Syaroslav@ivinco.com		$this->_mode = $mode;
7290Syaroslav@ivinco.com	}
7300Syaroslav@ivinco.com
7310Syaroslav@ivinco.com	/// set ranking mode
7320Syaroslav@ivinco.com	function SetRankingMode ( $ranker )
7330Syaroslav@ivinco.com	{
7340Syaroslav@ivinco.com		assert ( $ranker==SPH_RANK_PROXIMITY_BM25
7350Syaroslav@ivinco.com			|| $ranker==SPH_RANK_BM25
7360Syaroslav@ivinco.com			|| $ranker==SPH_RANK_NONE
7370Syaroslav@ivinco.com			|| $ranker==SPH_RANK_WORDCOUNT
7380Syaroslav@ivinco.com			|| $ranker==SPH_RANK_PROXIMITY );
7390Syaroslav@ivinco.com		$this->_ranker = $ranker;
7400Syaroslav@ivinco.com	}
7410Syaroslav@ivinco.com
7420Syaroslav@ivinco.com	/// set matches sorting mode
7430Syaroslav@ivinco.com	function SetSortMode ( $mode, $sortby="" )
7440Syaroslav@ivinco.com	{
7450Syaroslav@ivinco.com		assert (
7460Syaroslav@ivinco.com			$mode==SPH_SORT_RELEVANCE ||
7470Syaroslav@ivinco.com			$mode==SPH_SORT_ATTR_DESC ||
7480Syaroslav@ivinco.com			$mode==SPH_SORT_ATTR_ASC ||
7490Syaroslav@ivinco.com			$mode==SPH_SORT_TIME_SEGMENTS ||
7500Syaroslav@ivinco.com			$mode==SPH_SORT_EXTENDED ||
7510Syaroslav@ivinco.com			$mode==SPH_SORT_EXPR );
7520Syaroslav@ivinco.com		assert ( is_string($sortby) );
7530Syaroslav@ivinco.com		assert ( $mode==SPH_SORT_RELEVANCE || strlen($sortby)>0 );
7540Syaroslav@ivinco.com
7550Syaroslav@ivinco.com		$this->_sort = $mode;
7560Syaroslav@ivinco.com		$this->_sortby = $sortby;
7570Syaroslav@ivinco.com	}
7580Syaroslav@ivinco.com
7590Syaroslav@ivinco.com	/// bind per-field weights by order
7600Syaroslav@ivinco.com	/// DEPRECATED; use SetFieldWeights() instead
7610Syaroslav@ivinco.com	function SetWeights ( $weights )
7620Syaroslav@ivinco.com	{
7630Syaroslav@ivinco.com		assert ( is_array($weights) );
7640Syaroslav@ivinco.com		foreach ( $weights as $weight )
7650Syaroslav@ivinco.com			assert ( is_int($weight) );
7660Syaroslav@ivinco.com
7670Syaroslav@ivinco.com		$this->_weights = $weights;
7680Syaroslav@ivinco.com	}
7690Syaroslav@ivinco.com
7700Syaroslav@ivinco.com	/// bind per-field weights by name
7710Syaroslav@ivinco.com	function SetFieldWeights ( $weights )
7720Syaroslav@ivinco.com	{
7730Syaroslav@ivinco.com		assert ( is_array($weights) );
7740Syaroslav@ivinco.com		foreach ( $weights as $name=>$weight )
7750Syaroslav@ivinco.com		{
7760Syaroslav@ivinco.com			assert ( is_string($name) );
7770Syaroslav@ivinco.com			assert ( is_int($weight) );
7780Syaroslav@ivinco.com		}
7790Syaroslav@ivinco.com		$this->_fieldweights = $weights;
7800Syaroslav@ivinco.com	}
7810Syaroslav@ivinco.com
7820Syaroslav@ivinco.com	/// bind per-index weights by name
7830Syaroslav@ivinco.com	function SetIndexWeights ( $weights )
7840Syaroslav@ivinco.com	{
7850Syaroslav@ivinco.com		assert ( is_array($weights) );
7860Syaroslav@ivinco.com		foreach ( $weights as $index=>$weight )
7870Syaroslav@ivinco.com		{
7880Syaroslav@ivinco.com			assert ( is_string($index) );
7890Syaroslav@ivinco.com			assert ( is_int($weight) );
7900Syaroslav@ivinco.com		}
7910Syaroslav@ivinco.com		$this->_indexweights = $weights;
7920Syaroslav@ivinco.com	}
7930Syaroslav@ivinco.com
7940Syaroslav@ivinco.com	/// set IDs range to match
7950Syaroslav@ivinco.com	/// only match records if document ID is beetwen $min and $max (inclusive)
7960Syaroslav@ivinco.com	function SetIDRange ( $min, $max )
7970Syaroslav@ivinco.com	{
7980Syaroslav@ivinco.com		assert ( is_numeric($min) );
7990Syaroslav@ivinco.com		assert ( is_numeric($max) );
8000Syaroslav@ivinco.com		assert ( $min<=$max );
8010Syaroslav@ivinco.com		$this->_min_id = $min;
8020Syaroslav@ivinco.com		$this->_max_id = $max;
8030Syaroslav@ivinco.com	}
8040Syaroslav@ivinco.com
8050Syaroslav@ivinco.com	/// set values set filter
8060Syaroslav@ivinco.com	/// only match records where $attribute value is in given set
8070Syaroslav@ivinco.com	function SetFilter ( $attribute, $values, $exclude=false )
8080Syaroslav@ivinco.com	{
8090Syaroslav@ivinco.com		assert ( is_string($attribute) );
8100Syaroslav@ivinco.com		assert ( is_array($values) );
8110Syaroslav@ivinco.com		assert ( count($values) );
8120Syaroslav@ivinco.com
8130Syaroslav@ivinco.com		if ( is_array($values) && count($values) )
8140Syaroslav@ivinco.com		{
8150Syaroslav@ivinco.com			foreach ( $values as $value )
8160Syaroslav@ivinco.com				assert ( is_numeric($value) );
8170Syaroslav@ivinco.com
8180Syaroslav@ivinco.com			$this->_filters[] = array ( "type"=>SPH_FILTER_VALUES, "attr"=>$attribute, "exclude"=>$exclude, "values"=>$values );
8190Syaroslav@ivinco.com		}
8200Syaroslav@ivinco.com	}
8210Syaroslav@ivinco.com
8220Syaroslav@ivinco.com	/// set range filter
8230Syaroslav@ivinco.com	/// only match records if $attribute value is beetwen $min and $max (inclusive)
8240Syaroslav@ivinco.com	function SetFilterRange ( $attribute, $min, $max, $exclude=false )
8250Syaroslav@ivinco.com	{
8260Syaroslav@ivinco.com		assert ( is_string($attribute) );
8270Syaroslav@ivinco.com		assert ( is_numeric($min) );
8280Syaroslav@ivinco.com		assert ( is_numeric($max) );
8290Syaroslav@ivinco.com		assert ( $min<=$max );
8300Syaroslav@ivinco.com
8310Syaroslav@ivinco.com		$this->_filters[] = array ( "type"=>SPH_FILTER_RANGE, "attr"=>$attribute, "exclude"=>$exclude, "min"=>$min, "max"=>$max );
8320Syaroslav@ivinco.com	}
8330Syaroslav@ivinco.com
8340Syaroslav@ivinco.com	/// set float range filter
8350Syaroslav@ivinco.com	/// only match records if $attribute value is beetwen $min and $max (inclusive)
8360Syaroslav@ivinco.com	function SetFilterFloatRange ( $attribute, $min, $max, $exclude=false )
8370Syaroslav@ivinco.com	{
8380Syaroslav@ivinco.com		assert ( is_string($attribute) );
8390Syaroslav@ivinco.com		assert ( is_float($min) );
8400Syaroslav@ivinco.com		assert ( is_float($max) );
8410Syaroslav@ivinco.com		assert ( $min<=$max );
8420Syaroslav@ivinco.com
8430Syaroslav@ivinco.com		$this->_filters[] = array ( "type"=>SPH_FILTER_FLOATRANGE, "attr"=>$attribute, "exclude"=>$exclude, "min"=>$min, "max"=>$max );
8440Syaroslav@ivinco.com	}
8450Syaroslav@ivinco.com
8460Syaroslav@ivinco.com	/// setup anchor point for geosphere distance calculations
8470Syaroslav@ivinco.com	/// required to use @geodist in filters and sorting
8480Syaroslav@ivinco.com	/// latitude and longitude must be in radians
8490Syaroslav@ivinco.com	function SetGeoAnchor ( $attrlat, $attrlong, $lat, $long )
8500Syaroslav@ivinco.com	{
8510Syaroslav@ivinco.com		assert ( is_string($attrlat) );
8520Syaroslav@ivinco.com		assert ( is_string($attrlong) );
8530Syaroslav@ivinco.com		assert ( is_float($lat) );
8540Syaroslav@ivinco.com		assert ( is_float($long) );
8550Syaroslav@ivinco.com
8560Syaroslav@ivinco.com		$this->_anchor = array ( "attrlat"=>$attrlat, "attrlong"=>$attrlong, "lat"=>$lat, "long"=>$long );
8570Syaroslav@ivinco.com	}
8580Syaroslav@ivinco.com
8590Syaroslav@ivinco.com	/// set grouping attribute and function
8600Syaroslav@ivinco.com	function SetGroupBy ( $attribute, $func, $groupsort="@group desc" )
8610Syaroslav@ivinco.com	{
8620Syaroslav@ivinco.com		assert ( is_string($attribute) );
8630Syaroslav@ivinco.com		assert ( is_string($groupsort) );
8640Syaroslav@ivinco.com		assert ( $func==SPH_GROUPBY_DAY
8650Syaroslav@ivinco.com			|| $func==SPH_GROUPBY_WEEK
8660Syaroslav@ivinco.com			|| $func==SPH_GROUPBY_MONTH
8670Syaroslav@ivinco.com			|| $func==SPH_GROUPBY_YEAR
8680Syaroslav@ivinco.com			|| $func==SPH_GROUPBY_ATTR
8690Syaroslav@ivinco.com			|| $func==SPH_GROUPBY_ATTRPAIR );
8700Syaroslav@ivinco.com
8710Syaroslav@ivinco.com		$this->_groupby = $attribute;
8720Syaroslav@ivinco.com		$this->_groupfunc = $func;
8730Syaroslav@ivinco.com		$this->_groupsort = $groupsort;
8740Syaroslav@ivinco.com	}
8750Syaroslav@ivinco.com
8760Syaroslav@ivinco.com	/// set count-distinct attribute for group-by queries
8770Syaroslav@ivinco.com	function SetGroupDistinct ( $attribute )
8780Syaroslav@ivinco.com	{
8790Syaroslav@ivinco.com		assert ( is_string($attribute) );
8800Syaroslav@ivinco.com		$this->_groupdistinct = $attribute;
8810Syaroslav@ivinco.com	}
8820Syaroslav@ivinco.com
8830Syaroslav@ivinco.com	/// set distributed retries count and delay
8840Syaroslav@ivinco.com	function SetRetries ( $count, $delay=0 )
8850Syaroslav@ivinco.com	{
8860Syaroslav@ivinco.com		assert ( is_int($count) && $count>=0 );
8870Syaroslav@ivinco.com		assert ( is_int($delay) && $delay>=0 );
8880Syaroslav@ivinco.com		$this->_retrycount = $count;
8890Syaroslav@ivinco.com		$this->_retrydelay = $delay;
8900Syaroslav@ivinco.com	}
8910Syaroslav@ivinco.com
8920Syaroslav@ivinco.com	/// set result set format (hash or array; hash by default)
8930Syaroslav@ivinco.com	/// PHP specific; needed for group-by-MVA result sets that may contain duplicate IDs
8940Syaroslav@ivinco.com	function SetArrayResult ( $arrayresult )
8950Syaroslav@ivinco.com	{
8960Syaroslav@ivinco.com		assert ( is_bool($arrayresult) );
8970Syaroslav@ivinco.com		$this->_arrayresult = $arrayresult;
8980Syaroslav@ivinco.com	}
8990Syaroslav@ivinco.com
9000Syaroslav@ivinco.com	/// set attribute values override
9010Syaroslav@ivinco.com	/// there can be only one override per attribute
9020Syaroslav@ivinco.com	/// $values must be a hash that maps document IDs to attribute values
9030Syaroslav@ivinco.com	function SetOverride ( $attrname, $attrtype, $values )
9040Syaroslav@ivinco.com	{
9050Syaroslav@ivinco.com		assert ( is_string ( $attrname ) );
9060Syaroslav@ivinco.com		assert ( in_array ( $attrtype, array ( SPH_ATTR_INTEGER, SPH_ATTR_TIMESTAMP, SPH_ATTR_BOOL, SPH_ATTR_FLOAT, SPH_ATTR_BIGINT ) ) );
9070Syaroslav@ivinco.com		assert ( is_array ( $values ) );
9080Syaroslav@ivinco.com
9090Syaroslav@ivinco.com		$this->_overrides[$attrname] = array ( "attr"=>$attrname, "type"=>$attrtype, "values"=>$values );
9100Syaroslav@ivinco.com	}
9110Syaroslav@ivinco.com
9120Syaroslav@ivinco.com	/// set select-list (attributes or expressions), SQL-like syntax
9130Syaroslav@ivinco.com	function SetSelect ( $select )
9140Syaroslav@ivinco.com	{
9150Syaroslav@ivinco.com		assert ( is_string ( $select ) );
9160Syaroslav@ivinco.com		$this->_select = $select;
9170Syaroslav@ivinco.com	}
9180Syaroslav@ivinco.com
9190Syaroslav@ivinco.com	//////////////////////////////////////////////////////////////////////////////
9200Syaroslav@ivinco.com
9210Syaroslav@ivinco.com	/// clear all filters (for multi-queries)
9220Syaroslav@ivinco.com	function ResetFilters ()
9230Syaroslav@ivinco.com	{
9240Syaroslav@ivinco.com		$this->_filters = array();
9250Syaroslav@ivinco.com		$this->_anchor = array();
9260Syaroslav@ivinco.com	}
9270Syaroslav@ivinco.com
9280Syaroslav@ivinco.com	/// clear groupby settings (for multi-queries)
9290Syaroslav@ivinco.com	function ResetGroupBy ()
9300Syaroslav@ivinco.com	{
9310Syaroslav@ivinco.com		$this->_groupby		= "";
9320Syaroslav@ivinco.com		$this->_groupfunc	= SPH_GROUPBY_DAY;
9330Syaroslav@ivinco.com		$this->_groupsort	= "@group desc";
9340Syaroslav@ivinco.com		$this->_groupdistinct= "";
9350Syaroslav@ivinco.com	}
9360Syaroslav@ivinco.com
9370Syaroslav@ivinco.com	/// clear all attribute value overrides (for multi-queries)
9380Syaroslav@ivinco.com	function ResetOverrides ()
9390Syaroslav@ivinco.com    {
9400Syaroslav@ivinco.com    	$this->_overrides = array ();
9410Syaroslav@ivinco.com    }
9420Syaroslav@ivinco.com
9430Syaroslav@ivinco.com	//////////////////////////////////////////////////////////////////////////////
9440Syaroslav@ivinco.com
9450Syaroslav@ivinco.com	/// connect to searchd server, run given search query through given indexes,
9460Syaroslav@ivinco.com	/// and return the search results
9470Syaroslav@ivinco.com	function Query ( $query, $index="*", $comment="" )
9480Syaroslav@ivinco.com	{
9490Syaroslav@ivinco.com		assert ( empty($this->_reqs) );
9500Syaroslav@ivinco.com
9510Syaroslav@ivinco.com		$this->AddQuery ( $query, $index, $comment );
9520Syaroslav@ivinco.com		$results = $this->RunQueries ();
9530Syaroslav@ivinco.com		$this->_reqs = array (); // just in case it failed too early
9540Syaroslav@ivinco.com
9550Syaroslav@ivinco.com		if ( !is_array($results) )
9560Syaroslav@ivinco.com			return false; // probably network error; error message should be already filled
9570Syaroslav@ivinco.com
9580Syaroslav@ivinco.com		$this->_error = $results[0]["error"];
9590Syaroslav@ivinco.com		$this->_warning = $results[0]["warning"];
9600Syaroslav@ivinco.com		if ( $results[0]["status"]==SEARCHD_ERROR )
9610Syaroslav@ivinco.com			return false;
9620Syaroslav@ivinco.com		else
9630Syaroslav@ivinco.com			return $results[0];
9640Syaroslav@ivinco.com	}
9650Syaroslav@ivinco.com
9660Syaroslav@ivinco.com	/// helper to pack floats in network byte order
9670Syaroslav@ivinco.com	function _PackFloat ( $f )
9680Syaroslav@ivinco.com	{
9690Syaroslav@ivinco.com		$t1 = pack ( "f", $f ); // machine order
9700Syaroslav@ivinco.com		list(,$t2) = unpack ( "L*", $t1 ); // int in machine order
9710Syaroslav@ivinco.com		return pack ( "N", $t2 );
9720Syaroslav@ivinco.com	}
9730Syaroslav@ivinco.com
9740Syaroslav@ivinco.com	/// add query to multi-query batch
9750Syaroslav@ivinco.com	/// returns index into results array from RunQueries() call
9760Syaroslav@ivinco.com	function AddQuery ( $query, $index="*", $comment="" )
9770Syaroslav@ivinco.com	{
9780Syaroslav@ivinco.com		// mbstring workaround
9790Syaroslav@ivinco.com		$this->_MBPush ();
9800Syaroslav@ivinco.com
9810Syaroslav@ivinco.com		// build request
9820Syaroslav@ivinco.com		$req = pack ( "NNNNN", $this->_offset, $this->_limit, $this->_mode, $this->_ranker, $this->_sort ); // mode and limits
9830Syaroslav@ivinco.com		$req .= pack ( "N", strlen($this->_sortby) ) . $this->_sortby;
9840Syaroslav@ivinco.com		$req .= pack ( "N", strlen($query) ) . $query; // query itself
9850Syaroslav@ivinco.com		$req .= pack ( "N", count($this->_weights) ); // weights
9860Syaroslav@ivinco.com		foreach ( $this->_weights as $weight )
9870Syaroslav@ivinco.com			$req .= pack ( "N", (int)$weight );
9880Syaroslav@ivinco.com		$req .= pack ( "N", strlen($index) ) . $index; // indexes
9890Syaroslav@ivinco.com		$req .= pack ( "N", 1 ); // id64 range marker
9900Syaroslav@ivinco.com		$req .= sphPackU64 ( $this->_min_id ) . sphPackU64 ( $this->_max_id ); // id64 range
9910Syaroslav@ivinco.com
9920Syaroslav@ivinco.com		// filters
9930Syaroslav@ivinco.com		$req .= pack ( "N", count($this->_filters) );
9940Syaroslav@ivinco.com		foreach ( $this->_filters as $filter )
9950Syaroslav@ivinco.com		{
9960Syaroslav@ivinco.com			$req .= pack ( "N", strlen($filter["attr"]) ) . $filter["attr"];
9970Syaroslav@ivinco.com			$req .= pack ( "N", $filter["type"] );
9980Syaroslav@ivinco.com			switch ( $filter["type"] )
9990Syaroslav@ivinco.com			{
10000Syaroslav@ivinco.com				case SPH_FILTER_VALUES:
10010Syaroslav@ivinco.com					$req .= pack ( "N", count($filter["values"]) );
10020Syaroslav@ivinco.com					foreach ( $filter["values"] as $value )
10030Syaroslav@ivinco.com						$req .= sphPackI64 ( $value );
10040Syaroslav@ivinco.com					break;
10050Syaroslav@ivinco.com
10060Syaroslav@ivinco.com				case SPH_FILTER_RANGE:
10070Syaroslav@ivinco.com					$req .= sphPackI64 ( $filter["min"] ) . sphPackI64 ( $filter["max"] );
10080Syaroslav@ivinco.com					break;
10090Syaroslav@ivinco.com
10100Syaroslav@ivinco.com				case SPH_FILTER_FLOATRANGE:
10110Syaroslav@ivinco.com					$req .= $this->_PackFloat ( $filter["min"] ) . $this->_PackFloat ( $filter["max"] );
10120Syaroslav@ivinco.com					break;
10130Syaroslav@ivinco.com
10140Syaroslav@ivinco.com				default:
10150Syaroslav@ivinco.com					assert ( 0 && "internal error: unhandled filter type" );
10160Syaroslav@ivinco.com			}
10170Syaroslav@ivinco.com			$req .= pack ( "N", $filter["exclude"] );
10180Syaroslav@ivinco.com		}
10190Syaroslav@ivinco.com
10200Syaroslav@ivinco.com		// group-by clause, max-matches count, group-sort clause, cutoff count
10210Syaroslav@ivinco.com		$req .= pack ( "NN", $this->_groupfunc, strlen($this->_groupby) ) . $this->_groupby;
10220Syaroslav@ivinco.com		$req .= pack ( "N", $this->_maxmatches );
10230Syaroslav@ivinco.com		$req .= pack ( "N", strlen($this->_groupsort) ) . $this->_groupsort;
10240Syaroslav@ivinco.com		$req .= pack ( "NNN", $this->_cutoff, $this->_retrycount, $this->_retrydelay );
10250Syaroslav@ivinco.com		$req .= pack ( "N", strlen($this->_groupdistinct) ) . $this->_groupdistinct;
10260Syaroslav@ivinco.com
10270Syaroslav@ivinco.com		// anchor point
10280Syaroslav@ivinco.com		if ( empty($this->_anchor) )
10290Syaroslav@ivinco.com		{
10300Syaroslav@ivinco.com			$req .= pack ( "N", 0 );
10310Syaroslav@ivinco.com		} else
10320Syaroslav@ivinco.com		{
10330Syaroslav@ivinco.com			$a =& $this->_anchor;
10340Syaroslav@ivinco.com			$req .= pack ( "N", 1 );
10350Syaroslav@ivinco.com			$req .= pack ( "N", strlen($a["attrlat"]) ) . $a["attrlat"];
10360Syaroslav@ivinco.com			$req .= pack ( "N", strlen($a["attrlong"]) ) . $a["attrlong"];
10370Syaroslav@ivinco.com			$req .= $this->_PackFloat ( $a["lat"] ) . $this->_PackFloat ( $a["long"] );
10380Syaroslav@ivinco.com		}
10390Syaroslav@ivinco.com
10400Syaroslav@ivinco.com		// per-index weights
10410Syaroslav@ivinco.com		$req .= pack ( "N", count($this->_indexweights) );
10420Syaroslav@ivinco.com		foreach ( $this->_indexweights as $idx=>$weight )
10430Syaroslav@ivinco.com			$req .= pack ( "N", strlen($idx) ) . $idx . pack ( "N", $weight );
10440Syaroslav@ivinco.com
10450Syaroslav@ivinco.com		// max query time
10460Syaroslav@ivinco.com		$req .= pack ( "N", $this->_maxquerytime );
10470Syaroslav@ivinco.com
10480Syaroslav@ivinco.com		// per-field weights
10490Syaroslav@ivinco.com		$req .= pack ( "N", count($this->_fieldweights) );
10500Syaroslav@ivinco.com		foreach ( $this->_fieldweights as $field=>$weight )
10510Syaroslav@ivinco.com			$req .= pack ( "N", strlen($field) ) . $field . pack ( "N", $weight );
10520Syaroslav@ivinco.com
10530Syaroslav@ivinco.com		// comment
10540Syaroslav@ivinco.com		$req .= pack ( "N", strlen($comment) ) . $comment;
10550Syaroslav@ivinco.com
10560Syaroslav@ivinco.com		// attribute overrides
10570Syaroslav@ivinco.com		$req .= pack ( "N", count($this->_overrides) );
10580Syaroslav@ivinco.com		foreach ( $this->_overrides as $key => $entry )
10590Syaroslav@ivinco.com		{
10600Syaroslav@ivinco.com			$req .= pack ( "N", strlen($entry["attr"]) ) . $entry["attr"];
10610Syaroslav@ivinco.com			$req .= pack ( "NN", $entry["type"], count($entry["values"]) );
10620Syaroslav@ivinco.com			foreach ( $entry["values"] as $id=>$val )
10630Syaroslav@ivinco.com			{
10640Syaroslav@ivinco.com				assert ( is_numeric($id) );
10650Syaroslav@ivinco.com				assert ( is_numeric($val) );
10660Syaroslav@ivinco.com
10670Syaroslav@ivinco.com				$req .= sphPackU64 ( $id );
10680Syaroslav@ivinco.com				switch ( $entry["type"] )
10690Syaroslav@ivinco.com				{
10700Syaroslav@ivinco.com					case SPH_ATTR_FLOAT:	$req .= $this->_PackFloat ( $val ); break;
10710Syaroslav@ivinco.com					case SPH_ATTR_BIGINT:	$req .= sphPackI64 ( $val ); break;
10720Syaroslav@ivinco.com					default:				$req .= pack ( "N", $val ); break;
10730Syaroslav@ivinco.com				}
10740Syaroslav@ivinco.com			}
10750Syaroslav@ivinco.com		}
10760Syaroslav@ivinco.com
10770Syaroslav@ivinco.com		// select-list
10780Syaroslav@ivinco.com		$req .= pack ( "N", strlen($this->_select) ) . $this->_select;
10790Syaroslav@ivinco.com
10800Syaroslav@ivinco.com		// mbstring workaround
10810Syaroslav@ivinco.com		$this->_MBPop ();
10820Syaroslav@ivinco.com
10830Syaroslav@ivinco.com		// store request to requests array
10840Syaroslav@ivinco.com		$this->_reqs[] = $req;
10850Syaroslav@ivinco.com		return count($this->_reqs)-1;
10860Syaroslav@ivinco.com	}
10870Syaroslav@ivinco.com
10880Syaroslav@ivinco.com	/// connect to searchd, run queries batch, and return an array of result sets
10890Syaroslav@ivinco.com	function RunQueries ()
10900Syaroslav@ivinco.com	{
10910Syaroslav@ivinco.com		if ( empty($this->_reqs) )
10920Syaroslav@ivinco.com		{
10930Syaroslav@ivinco.com			$this->_error = "no queries defined, issue AddQuery() first";
10940Syaroslav@ivinco.com			return false;
10950Syaroslav@ivinco.com		}
10960Syaroslav@ivinco.com
10970Syaroslav@ivinco.com		// mbstring workaround
10980Syaroslav@ivinco.com		$this->_MBPush ();
10990Syaroslav@ivinco.com
11000Syaroslav@ivinco.com		if (!( $fp = $this->_Connect() ))
11010Syaroslav@ivinco.com		{
11020Syaroslav@ivinco.com			$this->_MBPop ();
11030Syaroslav@ivinco.com			return false;
11040Syaroslav@ivinco.com		}
11050Syaroslav@ivinco.com
11060Syaroslav@ivinco.com		// send query, get response
11070Syaroslav@ivinco.com		$nreqs = count($this->_reqs);
11080Syaroslav@ivinco.com		$req = join ( "", $this->_reqs );
11090Syaroslav@ivinco.com		$len = 4+strlen($req);
11100Syaroslav@ivinco.com		$req = pack ( "nnNN", SEARCHD_COMMAND_SEARCH, VER_COMMAND_SEARCH, $len, $nreqs ) . $req; // add header
11110Syaroslav@ivinco.com
11120Syaroslav@ivinco.com		if ( !( $this->_Send ( $fp, $req, $len+8 ) ) ||
11130Syaroslav@ivinco.com			 !( $response = $this->_GetResponse ( $fp, VER_COMMAND_SEARCH ) ) )
11140Syaroslav@ivinco.com		{
11150Syaroslav@ivinco.com			$this->_MBPop ();
11160Syaroslav@ivinco.com			return false;
11170Syaroslav@ivinco.com		}
11180Syaroslav@ivinco.com
11190Syaroslav@ivinco.com		// query sent ok; we can reset reqs now
11200Syaroslav@ivinco.com		$this->_reqs = array ();
11210Syaroslav@ivinco.com
11220Syaroslav@ivinco.com		// parse and return response
11230Syaroslav@ivinco.com		return $this->_ParseSearchResponse ( $response, $nreqs );
11240Syaroslav@ivinco.com	}
11250Syaroslav@ivinco.com
11260Syaroslav@ivinco.com	/// parse and return search query (or queries) response
11270Syaroslav@ivinco.com	function _ParseSearchResponse ( $response, $nreqs )
11280Syaroslav@ivinco.com	{
11290Syaroslav@ivinco.com		$p = 0; // current position
11300Syaroslav@ivinco.com		$max = strlen($response); // max position for checks, to protect against broken responses
11310Syaroslav@ivinco.com
11320Syaroslav@ivinco.com		$results = array ();
11330Syaroslav@ivinco.com		for ( $ires=0; $ires<$nreqs && $p<$max; $ires++ )
11340Syaroslav@ivinco.com		{
11350Syaroslav@ivinco.com			$results[] = array();
11360Syaroslav@ivinco.com			$result =& $results[$ires];
11370Syaroslav@ivinco.com
11380Syaroslav@ivinco.com			$result["error"] = "";
11390Syaroslav@ivinco.com			$result["warning"] = "";
11400Syaroslav@ivinco.com
11410Syaroslav@ivinco.com			// extract status
11420Syaroslav@ivinco.com			list(,$status) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
11430Syaroslav@ivinco.com			$result["status"] = $status;
11440Syaroslav@ivinco.com			if ( $status!=SEARCHD_OK )
11450Syaroslav@ivinco.com			{
11460Syaroslav@ivinco.com				list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
11470Syaroslav@ivinco.com				$message = substr ( $response, $p, $len ); $p += $len;
11480Syaroslav@ivinco.com
11490Syaroslav@ivinco.com				if ( $status==SEARCHD_WARNING )
11500Syaroslav@ivinco.com				{
11510Syaroslav@ivinco.com					$result["warning"] = $message;
11520Syaroslav@ivinco.com				} else
11530Syaroslav@ivinco.com				{
11540Syaroslav@ivinco.com					$result["error"] = $message;
11550Syaroslav@ivinco.com					continue;
11560Syaroslav@ivinco.com				}
11570Syaroslav@ivinco.com			}
11580Syaroslav@ivinco.com
11590Syaroslav@ivinco.com			// read schema
11600Syaroslav@ivinco.com			$fields = array ();
11610Syaroslav@ivinco.com			$attrs = array ();
11620Syaroslav@ivinco.com
11630Syaroslav@ivinco.com			list(,$nfields) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
11640Syaroslav@ivinco.com			while ( $nfields-->0 && $p<$max )
11650Syaroslav@ivinco.com			{
11660Syaroslav@ivinco.com				list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
11670Syaroslav@ivinco.com				$fields[] = substr ( $response, $p, $len ); $p += $len;
11680Syaroslav@ivinco.com			}
11690Syaroslav@ivinco.com			$result["fields"] = $fields;
11700Syaroslav@ivinco.com
11710Syaroslav@ivinco.com			list(,$nattrs) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
11720Syaroslav@ivinco.com			while ( $nattrs-->0 && $p<$max  )
11730Syaroslav@ivinco.com			{
11740Syaroslav@ivinco.com				list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
11750Syaroslav@ivinco.com				$attr = substr ( $response, $p, $len ); $p += $len;
11760Syaroslav@ivinco.com				list(,$type) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
11770Syaroslav@ivinco.com				$attrs[$attr] = $type;
11780Syaroslav@ivinco.com			}
11790Syaroslav@ivinco.com			$result["attrs"] = $attrs;
11800Syaroslav@ivinco.com
11810Syaroslav@ivinco.com			// read match count
11820Syaroslav@ivinco.com			list(,$count) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
11830Syaroslav@ivinco.com			list(,$id64) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
11840Syaroslav@ivinco.com
11850Syaroslav@ivinco.com			// read matches
11860Syaroslav@ivinco.com			$idx = -1;
11870Syaroslav@ivinco.com			while ( $count-->0 && $p<$max )
11880Syaroslav@ivinco.com			{
11890Syaroslav@ivinco.com				// index into result array
11900Syaroslav@ivinco.com				$idx++;
11910Syaroslav@ivinco.com
11920Syaroslav@ivinco.com				// parse document id and weight
11930Syaroslav@ivinco.com				if ( $id64 )
11940Syaroslav@ivinco.com				{
11950Syaroslav@ivinco.com					$doc = sphUnpackU64 ( substr ( $response, $p, 8 ) ); $p += 8;
11960Syaroslav@ivinco.com					list(,$weight) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
11970Syaroslav@ivinco.com				}
11980Syaroslav@ivinco.com				else
11990Syaroslav@ivinco.com				{
12000Syaroslav@ivinco.com					list ( $doc, $weight ) = array_values ( unpack ( "N*N*",
12010Syaroslav@ivinco.com						substr ( $response, $p, 8 ) ) );
12020Syaroslav@ivinco.com					$p += 8;
12030Syaroslav@ivinco.com					$doc = sphFixUint($doc);
12040Syaroslav@ivinco.com				}
12050Syaroslav@ivinco.com				$weight = sprintf ( "%u", $weight );
12060Syaroslav@ivinco.com
12070Syaroslav@ivinco.com				// create match entry
12080Syaroslav@ivinco.com				if ( $this->_arrayresult )
12090Syaroslav@ivinco.com					$result["matches"][$idx] = array ( "id"=>$doc, "weight"=>$weight );
12100Syaroslav@ivinco.com				else
12110Syaroslav@ivinco.com					$result["matches"][$doc]["weight"] = $weight;
12120Syaroslav@ivinco.com
12130Syaroslav@ivinco.com				// parse and create attributes
12140Syaroslav@ivinco.com				$attrvals = array ();
12150Syaroslav@ivinco.com				foreach ( $attrs as $attr=>$type )
12160Syaroslav@ivinco.com				{
12170Syaroslav@ivinco.com					// handle 64bit ints
12180Syaroslav@ivinco.com					if ( $type==SPH_ATTR_BIGINT )
12190Syaroslav@ivinco.com					{
12200Syaroslav@ivinco.com						$attrvals[$attr] = sphUnpackI64 ( substr ( $response, $p, 8 ) ); $p += 8;
12210Syaroslav@ivinco.com						continue;
12220Syaroslav@ivinco.com					}
12230Syaroslav@ivinco.com
12240Syaroslav@ivinco.com					// handle floats
12250Syaroslav@ivinco.com					if ( $type==SPH_ATTR_FLOAT )
12260Syaroslav@ivinco.com					{
12270Syaroslav@ivinco.com						list(,$uval) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
12280Syaroslav@ivinco.com						list(,$fval) = unpack ( "f*", pack ( "L", $uval ) );
12290Syaroslav@ivinco.com						$attrvals[$attr] = $fval;
12300Syaroslav@ivinco.com						continue;
12310Syaroslav@ivinco.com					}
12320Syaroslav@ivinco.com
12330Syaroslav@ivinco.com					// handle everything else as unsigned ints
12340Syaroslav@ivinco.com					list(,$val) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
12350Syaroslav@ivinco.com					if ( $type & SPH_ATTR_MULTI )
12360Syaroslav@ivinco.com					{
12370Syaroslav@ivinco.com						$attrvals[$attr] = array ();
12380Syaroslav@ivinco.com						$nvalues = $val;
12390Syaroslav@ivinco.com						while ( $nvalues-->0 && $p<$max )
12400Syaroslav@ivinco.com						{
12410Syaroslav@ivinco.com							list(,$val) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
12420Syaroslav@ivinco.com							$attrvals[$attr][] = sphFixUint($val);
12430Syaroslav@ivinco.com						}
12440Syaroslav@ivinco.com					} else
12450Syaroslav@ivinco.com					{
12460Syaroslav@ivinco.com						$attrvals[$attr] = sphFixUint($val);
12470Syaroslav@ivinco.com					}
12480Syaroslav@ivinco.com				}
12490Syaroslav@ivinco.com
12500Syaroslav@ivinco.com				if ( $this->_arrayresult )
12510Syaroslav@ivinco.com					$result["matches"][$idx]["attrs"] = $attrvals;
12520Syaroslav@ivinco.com				else
12530Syaroslav@ivinco.com					$result["matches"][$doc]["attrs"] = $attrvals;
12540Syaroslav@ivinco.com			}
12550Syaroslav@ivinco.com
12560Syaroslav@ivinco.com			list ( $total, $total_found, $msecs, $words ) =
12570Syaroslav@ivinco.com				array_values ( unpack ( "N*N*N*N*", substr ( $response, $p, 16 ) ) );
12580Syaroslav@ivinco.com			$result["total"] = sprintf ( "%u", $total );
12590Syaroslav@ivinco.com			$result["total_found"] = sprintf ( "%u", $total_found );
12600Syaroslav@ivinco.com			$result["time"] = sprintf ( "%.3f", $msecs/1000 );
12610Syaroslav@ivinco.com			$p += 16;
12620Syaroslav@ivinco.com
12630Syaroslav@ivinco.com			while ( $words-->0 && $p<$max )
12640Syaroslav@ivinco.com			{
12650Syaroslav@ivinco.com				list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
12660Syaroslav@ivinco.com				$word = substr ( $response, $p, $len ); $p += $len;
12670Syaroslav@ivinco.com				list ( $docs, $hits ) = array_values ( unpack ( "N*N*", substr ( $response, $p, 8 ) ) ); $p += 8;
12680Syaroslav@ivinco.com				$result["words"][$word] = array (
12690Syaroslav@ivinco.com					"docs"=>sprintf ( "%u", $docs ),
12700Syaroslav@ivinco.com					"hits"=>sprintf ( "%u", $hits ) );
12710Syaroslav@ivinco.com			}
12720Syaroslav@ivinco.com		}
12730Syaroslav@ivinco.com
12740Syaroslav@ivinco.com		$this->_MBPop ();
12750Syaroslav@ivinco.com		return $results;
12760Syaroslav@ivinco.com	}
12770Syaroslav@ivinco.com
12780Syaroslav@ivinco.com	/////////////////////////////////////////////////////////////////////////////
12790Syaroslav@ivinco.com	// excerpts generation
12800Syaroslav@ivinco.com	/////////////////////////////////////////////////////////////////////////////
12810Syaroslav@ivinco.com
12820Syaroslav@ivinco.com	/// connect to searchd server, and generate exceprts (snippets)
12830Syaroslav@ivinco.com	/// of given documents for given query. returns false on failure,
12840Syaroslav@ivinco.com	/// an array of snippets on success
12850Syaroslav@ivinco.com	function BuildExcerpts ( $docs, $index, $words, $opts=array() )
12860Syaroslav@ivinco.com	{
12870Syaroslav@ivinco.com		assert ( is_array($docs) );
12880Syaroslav@ivinco.com		assert ( is_string($index) );
12890Syaroslav@ivinco.com		assert ( is_string($words) );
12900Syaroslav@ivinco.com		assert ( is_array($opts) );
12910Syaroslav@ivinco.com
12920Syaroslav@ivinco.com		$this->_MBPush ();
12930Syaroslav@ivinco.com
12940Syaroslav@ivinco.com		if (!( $fp = $this->_Connect() ))
12950Syaroslav@ivinco.com		{
12960Syaroslav@ivinco.com			$this->_MBPop();
12970Syaroslav@ivinco.com			return false;
12980Syaroslav@ivinco.com		}
12990Syaroslav@ivinco.com
13000Syaroslav@ivinco.com		/////////////////
13010Syaroslav@ivinco.com		// fixup options
13020Syaroslav@ivinco.com		/////////////////
13030Syaroslav@ivinco.com
13040Syaroslav@ivinco.com		if ( !isset($opts["before_match"]) )		$opts["before_match"] = "<b>";
13050Syaroslav@ivinco.com		if ( !isset($opts["after_match"]) )			$opts["after_match"] = "</b>";
13060Syaroslav@ivinco.com		if ( !isset($opts["chunk_separator"]) )		$opts["chunk_separator"] = " ... ";
13070Syaroslav@ivinco.com		if ( !isset($opts["limit"]) )				$opts["limit"] = 256;
13080Syaroslav@ivinco.com		if ( !isset($opts["around"]) )				$opts["around"] = 5;
13090Syaroslav@ivinco.com		if ( !isset($opts["exact_phrase"]) )		$opts["exact_phrase"] = false;
13100Syaroslav@ivinco.com		if ( !isset($opts["single_passage"]) )		$opts["single_passage"] = false;
13110Syaroslav@ivinco.com		if ( !isset($opts["use_boundaries"]) )		$opts["use_boundaries"] = false;
13120Syaroslav@ivinco.com		if ( !isset($opts["weight_order"]) )		$opts["weight_order"] = false;
13130Syaroslav@ivinco.com
13140Syaroslav@ivinco.com		/////////////////
13150Syaroslav@ivinco.com		// build request
13160Syaroslav@ivinco.com		/////////////////
13170Syaroslav@ivinco.com
13180Syaroslav@ivinco.com		// v.1.0 req
13190Syaroslav@ivinco.com		$flags = 1; // remove spaces
13200Syaroslav@ivinco.com		if ( $opts["exact_phrase"] )	$flags |= 2;
13210Syaroslav@ivinco.com		if ( $opts["single_passage"] )	$flags |= 4;
13220Syaroslav@ivinco.com		if ( $opts["use_boundaries"] )	$flags |= 8;
13230Syaroslav@ivinco.com		if ( $opts["weight_order"] )	$flags |= 16;
13240Syaroslav@ivinco.com		$req = pack ( "NN", 0, $flags ); // mode=0, flags=$flags
13250Syaroslav@ivinco.com		$req .= pack ( "N", strlen($index) ) . $index; // req index
13260Syaroslav@ivinco.com		$req .= pack ( "N", strlen($words) ) . $words; // req words
13270Syaroslav@ivinco.com
13280Syaroslav@ivinco.com		// options
13290Syaroslav@ivinco.com		$req .= pack ( "N", strlen($opts["before_match"]) ) . $opts["before_match"];
13300Syaroslav@ivinco.com		$req .= pack ( "N", strlen($opts["after_match"]) ) . $opts["after_match"];
13310Syaroslav@ivinco.com		$req .= pack ( "N", strlen($opts["chunk_separator"]) ) . $opts["chunk_separator"];
13320Syaroslav@ivinco.com		$req .= pack ( "N", (int)$opts["limit"] );
13330Syaroslav@ivinco.com		$req .= pack ( "N", (int)$opts["around"] );
13340Syaroslav@ivinco.com
13350Syaroslav@ivinco.com		// documents
13360Syaroslav@ivinco.com		$req .= pack ( "N", count($docs) );
13370Syaroslav@ivinco.com		foreach ( $docs as $doc )
13380Syaroslav@ivinco.com		{
13390Syaroslav@ivinco.com			assert ( is_string($doc) );
13400Syaroslav@ivinco.com			$req .= pack ( "N", strlen($doc) ) . $doc;
13410Syaroslav@ivinco.com		}
13420Syaroslav@ivinco.com
13430Syaroslav@ivinco.com		////////////////////////////
13440Syaroslav@ivinco.com		// send query, get response
13450Syaroslav@ivinco.com		////////////////////////////
13460Syaroslav@ivinco.com
13470Syaroslav@ivinco.com		$len = strlen($req);
13480Syaroslav@ivinco.com		$req = pack ( "nnN", SEARCHD_COMMAND_EXCERPT, VER_COMMAND_EXCERPT, $len ) . $req; // add header
13490Syaroslav@ivinco.com		if ( !( $this->_Send ( $fp, $req, $len+8 ) ) ||
13500Syaroslav@ivinco.com			 !( $response = $this->_GetResponse ( $fp, VER_COMMAND_EXCERPT ) ) )
13510Syaroslav@ivinco.com		{
13520Syaroslav@ivinco.com			$this->_MBPop ();
13530Syaroslav@ivinco.com			return false;
13540Syaroslav@ivinco.com		}
13550Syaroslav@ivinco.com
13560Syaroslav@ivinco.com		//////////////////
13570Syaroslav@ivinco.com		// parse response
13580Syaroslav@ivinco.com		//////////////////
13590Syaroslav@ivinco.com
13600Syaroslav@ivinco.com		$pos = 0;
13610Syaroslav@ivinco.com		$res = array ();
13620Syaroslav@ivinco.com		$rlen = strlen($response);
13630Syaroslav@ivinco.com		for ( $i=0; $i<count($docs); $i++ )
13640Syaroslav@ivinco.com		{
13650Syaroslav@ivinco.com			list(,$len) = unpack ( "N*", substr ( $response, $pos, 4 ) );
13660Syaroslav@ivinco.com			$pos += 4;
13670Syaroslav@ivinco.com
13680Syaroslav@ivinco.com			if ( $pos+$len > $rlen )
13690Syaroslav@ivinco.com			{
13700Syaroslav@ivinco.com				$this->_error = "incomplete reply";
13710Syaroslav@ivinco.com				$this->_MBPop ();
13720Syaroslav@ivinco.com				return false;
13730Syaroslav@ivinco.com			}
13740Syaroslav@ivinco.com			$res[] = $len ? substr ( $response, $pos, $len ) : "";
13750Syaroslav@ivinco.com			$pos += $len;
13760Syaroslav@ivinco.com		}
13770Syaroslav@ivinco.com
13780Syaroslav@ivinco.com		$this->_MBPop ();
13790Syaroslav@ivinco.com		return $res;
13800Syaroslav@ivinco.com	}
13810Syaroslav@ivinco.com
13820Syaroslav@ivinco.com
13830Syaroslav@ivinco.com	/////////////////////////////////////////////////////////////////////////////
13840Syaroslav@ivinco.com	// keyword generation
13850Syaroslav@ivinco.com	/////////////////////////////////////////////////////////////////////////////
13860Syaroslav@ivinco.com
13870Syaroslav@ivinco.com	/// connect to searchd server, and generate keyword list for a given query
13880Syaroslav@ivinco.com	/// returns false on failure,
13890Syaroslav@ivinco.com	/// an array of words on success
13900Syaroslav@ivinco.com	function BuildKeywords ( $query, $index, $hits )
13910Syaroslav@ivinco.com	{
13920Syaroslav@ivinco.com		assert ( is_string($query) );
13930Syaroslav@ivinco.com		assert ( is_string($index) );
13940Syaroslav@ivinco.com		assert ( is_bool($hits) );
13950Syaroslav@ivinco.com
13960Syaroslav@ivinco.com		$this->_MBPush ();
13970Syaroslav@ivinco.com
13980Syaroslav@ivinco.com		if (!( $fp = $this->_Connect() ))
13990Syaroslav@ivinco.com		{
14000Syaroslav@ivinco.com			$this->_MBPop();
14010Syaroslav@ivinco.com			return false;
14020Syaroslav@ivinco.com		}
14030Syaroslav@ivinco.com
14040Syaroslav@ivinco.com		/////////////////
14050Syaroslav@ivinco.com		// build request
14060Syaroslav@ivinco.com		/////////////////
14070Syaroslav@ivinco.com
14080Syaroslav@ivinco.com		// v.1.0 req
14090Syaroslav@ivinco.com		$req  = pack ( "N", strlen($query) ) . $query; // req query
14100Syaroslav@ivinco.com		$req .= pack ( "N", strlen($index) ) . $index; // req index
14110Syaroslav@ivinco.com		$req .= pack ( "N", (int)$hits );
14120Syaroslav@ivinco.com
14130Syaroslav@ivinco.com		////////////////////////////
14140Syaroslav@ivinco.com		// send query, get response
14150Syaroslav@ivinco.com		////////////////////////////
14160Syaroslav@ivinco.com
14170Syaroslav@ivinco.com		$len = strlen($req);
14180Syaroslav@ivinco.com		$req = pack ( "nnN", SEARCHD_COMMAND_KEYWORDS, VER_COMMAND_KEYWORDS, $len ) . $req; // add header
14190Syaroslav@ivinco.com		if ( !( $this->_Send ( $fp, $req, $len+8 ) ) ||
14200Syaroslav@ivinco.com			 !( $response = $this->_GetResponse ( $fp, VER_COMMAND_KEYWORDS ) ) )
14210Syaroslav@ivinco.com		{
14220Syaroslav@ivinco.com			$this->_MBPop ();
14230Syaroslav@ivinco.com			return false;
14240Syaroslav@ivinco.com		}
14250Syaroslav@ivinco.com
14260Syaroslav@ivinco.com		//////////////////
14270Syaroslav@ivinco.com		// parse response
14280Syaroslav@ivinco.com		//////////////////
14290Syaroslav@ivinco.com
14300Syaroslav@ivinco.com		$pos = 0;
14310Syaroslav@ivinco.com		$res = array ();
14320Syaroslav@ivinco.com		$rlen = strlen($response);
14330Syaroslav@ivinco.com		list(,$nwords) = unpack ( "N*", substr ( $response, $pos, 4 ) );
14340Syaroslav@ivinco.com		$pos += 4;
14350Syaroslav@ivinco.com		for ( $i=0; $i<$nwords; $i++ )
14360Syaroslav@ivinco.com		{
14370Syaroslav@ivinco.com			list(,$len) = unpack ( "N*", substr ( $response, $pos, 4 ) );	$pos += 4;
14380Syaroslav@ivinco.com			$tokenized = $len ? substr ( $response, $pos, $len ) : "";
14390Syaroslav@ivinco.com			$pos += $len;
14400Syaroslav@ivinco.com
14410Syaroslav@ivinco.com			list(,$len) = unpack ( "N*", substr ( $response, $pos, 4 ) );	$pos += 4;
14420Syaroslav@ivinco.com			$normalized = $len ? substr ( $response, $pos, $len ) : "";
14430Syaroslav@ivinco.com			$pos += $len;
14440Syaroslav@ivinco.com
14450Syaroslav@ivinco.com			$res[] = array ( "tokenized"=>$tokenized, "normalized"=>$normalized );
14460Syaroslav@ivinco.com
14470Syaroslav@ivinco.com			if ( $hits )
14480Syaroslav@ivinco.com			{
14490Syaroslav@ivinco.com				list($ndocs,$nhits) = array_values ( unpack ( "N*N*", substr ( $response, $pos, 8 ) ) );
14500Syaroslav@ivinco.com				$pos += 8;
14510Syaroslav@ivinco.com				$res [$i]["docs"] = $ndocs;
14520Syaroslav@ivinco.com				$res [$i]["hits"] = $nhits;
14530Syaroslav@ivinco.com			}
14540Syaroslav@ivinco.com
14550Syaroslav@ivinco.com			if ( $pos > $rlen )
14560Syaroslav@ivinco.com			{
14570Syaroslav@ivinco.com				$this->_error = "incomplete reply";
14580Syaroslav@ivinco.com				$this->_MBPop ();
14590Syaroslav@ivinco.com				return false;
14600Syaroslav@ivinco.com			}
14610Syaroslav@ivinco.com		}
14620Syaroslav@ivinco.com
14630Syaroslav@ivinco.com		$this->_MBPop ();
14640Syaroslav@ivinco.com		return $res;
14650Syaroslav@ivinco.com	}
14660Syaroslav@ivinco.com
14670Syaroslav@ivinco.com	function EscapeString ( $string )
14680Syaroslav@ivinco.com	{
14690Syaroslav@ivinco.com		$from = array ( '\\', '(',')','|','-','!','@','~','"','&', '/', '^', '$', '=' );
14700Syaroslav@ivinco.com		$to   = array ( '\\\\', '\(','\)','\|','\-','\!','\@','\~','\"', '\&', '\/', '\^', '\$', '\=' );
14710Syaroslav@ivinco.com
14720Syaroslav@ivinco.com		return str_replace ( $from, $to, $string );
14730Syaroslav@ivinco.com	}
14740Syaroslav@ivinco.com
14750Syaroslav@ivinco.com	/////////////////////////////////////////////////////////////////////////////
14760Syaroslav@ivinco.com	// attribute updates
14770Syaroslav@ivinco.com	/////////////////////////////////////////////////////////////////////////////
14780Syaroslav@ivinco.com
14790Syaroslav@ivinco.com	/// batch update given attributes in given rows in given indexes
14800Syaroslav@ivinco.com	/// returns amount of updated documents (0 or more) on success, or -1 on failure
14810Syaroslav@ivinco.com	function UpdateAttributes ( $index, $attrs, $values, $mva=false )
14820Syaroslav@ivinco.com	{
14830Syaroslav@ivinco.com		// verify everything
14840Syaroslav@ivinco.com		assert ( is_string($index) );
14850Syaroslav@ivinco.com		assert ( is_bool($mva) );
14860Syaroslav@ivinco.com
14870Syaroslav@ivinco.com		assert ( is_array($attrs) );
14880Syaroslav@ivinco.com		foreach ( $attrs as $attr )
14890Syaroslav@ivinco.com			assert ( is_string($attr) );
14900Syaroslav@ivinco.com
14910Syaroslav@ivinco.com		assert ( is_array($values) );
14920Syaroslav@ivinco.com		foreach ( $values as $id=>$entry )
14930Syaroslav@ivinco.com		{
14940Syaroslav@ivinco.com			assert ( is_numeric($id) );
14950Syaroslav@ivinco.com			assert ( is_array($entry) );
14960Syaroslav@ivinco.com			assert ( count($entry)==count($attrs) );
14970Syaroslav@ivinco.com			foreach ( $entry as $v )
14980Syaroslav@ivinco.com			{
14990Syaroslav@ivinco.com				if ( $mva )
15000Syaroslav@ivinco.com				{
15010Syaroslav@ivinco.com					assert ( is_array($v) );
15020Syaroslav@ivinco.com					foreach ( $v as $vv )
15030Syaroslav@ivinco.com						assert ( is_int($vv) );
15040Syaroslav@ivinco.com				} else
15050Syaroslav@ivinco.com					assert ( is_int($v) );
15060Syaroslav@ivinco.com			}
15070Syaroslav@ivinco.com		}
15080Syaroslav@ivinco.com
15090Syaroslav@ivinco.com		// build request
15100Syaroslav@ivinco.com		$req = pack ( "N", strlen($index) ) . $index;
15110Syaroslav@ivinco.com
15120Syaroslav@ivinco.com		$req .= pack ( "N", count($attrs) );
15130Syaroslav@ivinco.com		foreach ( $attrs as $attr )
15140Syaroslav@ivinco.com		{
15150Syaroslav@ivinco.com			$req .= pack ( "N", strlen($attr) ) . $attr;
15160Syaroslav@ivinco.com			$req .= pack ( "N", $mva ? 1 : 0 );
15170Syaroslav@ivinco.com		}
15180Syaroslav@ivinco.com
15190Syaroslav@ivinco.com		$req .= pack ( "N", count($values) );
15200Syaroslav@ivinco.com		foreach ( $values as $id=>$entry )
15210Syaroslav@ivinco.com		{
15220Syaroslav@ivinco.com			$req .= sphPackU64 ( $id );
15230Syaroslav@ivinco.com			foreach ( $entry as $v )
15240Syaroslav@ivinco.com			{
15250Syaroslav@ivinco.com				$req .= pack ( "N", $mva ? count($v) : $v );
15260Syaroslav@ivinco.com				if ( $mva )
15270Syaroslav@ivinco.com					foreach ( $v as $vv )
15280Syaroslav@ivinco.com						$req .= pack ( "N", $vv );
15290Syaroslav@ivinco.com			}
15300Syaroslav@ivinco.com		}
15310Syaroslav@ivinco.com
15320Syaroslav@ivinco.com		// connect, send query, get response
15330Syaroslav@ivinco.com		if (!( $fp = $this->_Connect() ))
15340Syaroslav@ivinco.com			return -1;
15350Syaroslav@ivinco.com
15360Syaroslav@ivinco.com		$len = strlen($req);
15370Syaroslav@ivinco.com		$req = pack ( "nnN", SEARCHD_COMMAND_UPDATE, VER_COMMAND_UPDATE, $len ) . $req; // add header
15380Syaroslav@ivinco.com		if ( !$this->_Send ( $fp, $req, $len+8 ) )
15390Syaroslav@ivinco.com			return -1;
15400Syaroslav@ivinco.com
15410Syaroslav@ivinco.com		if (!( $response = $this->_GetResponse ( $fp, VER_COMMAND_UPDATE ) ))
15420Syaroslav@ivinco.com			return -1;
15430Syaroslav@ivinco.com
15440Syaroslav@ivinco.com		// parse response
15450Syaroslav@ivinco.com		list(,$updated) = unpack ( "N*", substr ( $response, 0, 4 ) );
15460Syaroslav@ivinco.com		return $updated;
15470Syaroslav@ivinco.com	}
15480Syaroslav@ivinco.com
15490Syaroslav@ivinco.com	/////////////////////////////////////////////////////////////////////////////
15500Syaroslav@ivinco.com	// persistent connections
15510Syaroslav@ivinco.com	/////////////////////////////////////////////////////////////////////////////
15520Syaroslav@ivinco.com
15530Syaroslav@ivinco.com	function Open()
15540Syaroslav@ivinco.com	{
15550Syaroslav@ivinco.com		if ( $this->_socket !== false )
15560Syaroslav@ivinco.com		{
15570Syaroslav@ivinco.com			$this->_error = 'already connected';
15580Syaroslav@ivinco.com			return false;
15590Syaroslav@ivinco.com		}
15600Syaroslav@ivinco.com		if ( !$fp = $this->_Connect() )
15610Syaroslav@ivinco.com			return false;
15620Syaroslav@ivinco.com
15630Syaroslav@ivinco.com		// command, command version = 0, body length = 4, body = 1
15640Syaroslav@ivinco.com		$req = pack ( "nnNN", SEARCHD_COMMAND_PERSIST, 0, 4, 1 );
15650Syaroslav@ivinco.com		if ( !$this->_Send ( $fp, $req, 12 ) )
15660Syaroslav@ivinco.com			return false;
15670Syaroslav@ivinco.com
15680Syaroslav@ivinco.com		$this->_socket = $fp;
15690Syaroslav@ivinco.com		return true;
15700Syaroslav@ivinco.com	}
15710Syaroslav@ivinco.com
15720Syaroslav@ivinco.com	function Close()
15730Syaroslav@ivinco.com	{
15740Syaroslav@ivinco.com		if ( $this->_socket === false )
15750Syaroslav@ivinco.com		{
15760Syaroslav@ivinco.com			$this->_error = 'not connected';
15770Syaroslav@ivinco.com			return false;
15780Syaroslav@ivinco.com		}
15790Syaroslav@ivinco.com
15800Syaroslav@ivinco.com		fclose ( $this->_socket );
15810Syaroslav@ivinco.com		$this->_socket = false;
15820Syaroslav@ivinco.com
15830Syaroslav@ivinco.com		return true;
15840Syaroslav@ivinco.com	}
15850Syaroslav@ivinco.com
15860Syaroslav@ivinco.com	//////////////////////////////////////////////////////////////////////////
15870Syaroslav@ivinco.com	// status
15880Syaroslav@ivinco.com	//////////////////////////////////////////////////////////////////////////
15890Syaroslav@ivinco.com
15900Syaroslav@ivinco.com	function Status ()
15910Syaroslav@ivinco.com	{
15920Syaroslav@ivinco.com		$this->_MBPush ();
15930Syaroslav@ivinco.com		if (!( $fp = $this->_Connect() ))
15940Syaroslav@ivinco.com		{
15950Syaroslav@ivinco.com			$this->_MBPop();
15960Syaroslav@ivinco.com			return false;
15970Syaroslav@ivinco.com		}
15980Syaroslav@ivinco.com
15990Syaroslav@ivinco.com		$req = pack ( "nnNN", SEARCHD_COMMAND_STATUS, VER_COMMAND_STATUS, 4, 1 ); // len=4, body=1
16000Syaroslav@ivinco.com		if ( !( $this->_Send ( $fp, $req, 12 ) ) ||
16010Syaroslav@ivinco.com			 !( $response = $this->_GetResponse ( $fp, VER_COMMAND_STATUS ) ) )
16020Syaroslav@ivinco.com		{
16030Syaroslav@ivinco.com			$this->_MBPop ();
16040Syaroslav@ivinco.com			return false;
16050Syaroslav@ivinco.com		}
16060Syaroslav@ivinco.com
16070Syaroslav@ivinco.com		$res = substr ( $response, 4 ); // just ignore length, error handling, etc
16080Syaroslav@ivinco.com		$p = 0;
16090Syaroslav@ivinco.com		list ( $rows, $cols ) = array_values ( unpack ( "N*N*", substr ( $response, $p, 8 ) ) ); $p += 8;
16100Syaroslav@ivinco.com
16110Syaroslav@ivinco.com		$res = array();
16120Syaroslav@ivinco.com		for ( $i=0; $i<$rows; $i++ )
16130Syaroslav@ivinco.com			for ( $j=0; $j<$cols; $j++ )
16140Syaroslav@ivinco.com		{
16150Syaroslav@ivinco.com			list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
16160Syaroslav@ivinco.com			$res[$i][] = substr ( $response, $p, $len ); $p += $len;
16170Syaroslav@ivinco.com		}
16180Syaroslav@ivinco.com
16190Syaroslav@ivinco.com		$this->_MBPop ();
16200Syaroslav@ivinco.com		return $res;
16210Syaroslav@ivinco.com	}
16220Syaroslav@ivinco.com}
16230Syaroslav@ivinco.com
1624*133Sandrey}
1625*133Sandrey
16260Syaroslav@ivinco.com//
16270Syaroslav@ivinco.com// $Id: sphinxapi.php 2055 2009-11-06 23:09:58Z shodan $
16280Syaroslav@ivinco.com//
1629