xref: /plugin/sphinxsearch-was/sphinxapi.php (revision 133:aa595765bbfd)
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		$v = (int)$v;
1250Syaroslav@ivinco.com		return pack("NN", $v >> 32, $v & 0xFFFFFFFF);
1260Syaroslav@ivinco.com	}
1270Syaroslav@ivinco.com
1280Syaroslav@ivinco.com	// x32, int
1290Syaroslav@ivinco.com	if (is_int($v))
1300Syaroslav@ivinco.com		return pack("NN", $v < 0 ? -1 : 0, $v);
1310Syaroslav@ivinco.com
1320Syaroslav@ivinco.com	// x32, bcmath
1330Syaroslav@ivinco.com	if (function_exists("bcmul")) {
1340Syaroslav@ivinco.com		if (bccomp($v, 0) == -1)
1350Syaroslav@ivinco.com			$v = bcadd("18446744073709551616", $v);
1360Syaroslav@ivinco.com		$h = bcdiv($v, "4294967296", 0);
1370Syaroslav@ivinco.com		$l = bcmod($v, "4294967296");
1380Syaroslav@ivinco.com		return pack("NN", (float)$h, (float)$l); // conversion to float is intentional; int would lose 31st bit
1390Syaroslav@ivinco.com	}
1400Syaroslav@ivinco.com
1410Syaroslav@ivinco.com	// x32, no-bcmath
1420Syaroslav@ivinco.com	$p = max(0, strlen($v) - 13);
1430Syaroslav@ivinco.com	$lo = abs((float)substr($v, $p));
1440Syaroslav@ivinco.com	$hi = abs((float)substr($v, 0, $p));
1450Syaroslav@ivinco.com
1460Syaroslav@ivinco.com	$m = $lo + $hi * 1316134912.0; // (10 ^ 13) % (1 << 32) = 1316134912
1470Syaroslav@ivinco.com	$q = floor($m / 4294967296.0);
1480Syaroslav@ivinco.com	$l = $m - ($q * 4294967296.0);
1490Syaroslav@ivinco.com	$h = $hi * 2328.0 + $q; // (10 ^ 13) / (1 << 32) = 2328
1500Syaroslav@ivinco.com
1510Syaroslav@ivinco.com	if ($v < 0) {
1520Syaroslav@ivinco.com		if ($l == 0)
1530Syaroslav@ivinco.com			$h = 4294967296.0 - $h;
1540Syaroslav@ivinco.com		else {
1550Syaroslav@ivinco.com			$h = 4294967295.0 - $h;
1560Syaroslav@ivinco.com			$l = 4294967296.0 - $l;
1570Syaroslav@ivinco.com		}
1580Syaroslav@ivinco.com	}
1590Syaroslav@ivinco.com	return pack("NN", $h, $l);
1600Syaroslav@ivinco.com}
1610Syaroslav@ivinco.com
1620Syaroslav@ivinco.com/// pack 64-bit unsigned
1630Syaroslav@ivinco.comfunction sphPackU64($v)
1640Syaroslav@ivinco.com{
1650Syaroslav@ivinco.com	assert(is_numeric($v));
1660Syaroslav@ivinco.com
1670Syaroslav@ivinco.com	// x64
1680Syaroslav@ivinco.com	if (PHP_INT_SIZE >= 8) {
1690Syaroslav@ivinco.com		assert($v >= 0);
1700Syaroslav@ivinco.com
1710Syaroslav@ivinco.com		// x64, int
1720Syaroslav@ivinco.com		if (is_int($v))
1730Syaroslav@ivinco.com			return pack("NN", $v >> 32, $v & 0xFFFFFFFF);
1740Syaroslav@ivinco.com
1750Syaroslav@ivinco.com		// x64, bcmath
1760Syaroslav@ivinco.com		if (function_exists("bcmul")) {
1770Syaroslav@ivinco.com			$h = bcdiv($v, 4294967296, 0);
1780Syaroslav@ivinco.com			$l = bcmod($v, 4294967296);
1790Syaroslav@ivinco.com			return pack("NN", $h, $l);
1800Syaroslav@ivinco.com		}
1810Syaroslav@ivinco.com
1820Syaroslav@ivinco.com		// x64, no-bcmath
1830Syaroslav@ivinco.com		$p = max(0, strlen($v) - 13);
1840Syaroslav@ivinco.com		$lo = (int)substr($v, $p);
1850Syaroslav@ivinco.com		$hi = (int)substr($v, 0, $p);
1860Syaroslav@ivinco.com
1870Syaroslav@ivinco.com		$m = $lo + $hi * 1316134912;
1880Syaroslav@ivinco.com		$l = $m % 4294967296;
1890Syaroslav@ivinco.com		$h = $hi * 2328 + (int)($m / 4294967296);
1900Syaroslav@ivinco.com
1910Syaroslav@ivinco.com		return pack("NN", $h, $l);
1920Syaroslav@ivinco.com	}
1930Syaroslav@ivinco.com
1940Syaroslav@ivinco.com	// x32, int
1950Syaroslav@ivinco.com	if (is_int($v))
1960Syaroslav@ivinco.com		return pack("NN", 0, $v);
1970Syaroslav@ivinco.com
1980Syaroslav@ivinco.com	// x32, bcmath
1990Syaroslav@ivinco.com	if (function_exists("bcmul")) {
2000Syaroslav@ivinco.com		$h = bcdiv($v, "4294967296", 0);
2010Syaroslav@ivinco.com		$l = bcmod($v, "4294967296");
2020Syaroslav@ivinco.com		return pack("NN", (float)$h, (float)$l); // conversion to float is intentional; int would lose 31st bit
2030Syaroslav@ivinco.com	}
2040Syaroslav@ivinco.com
2050Syaroslav@ivinco.com	// x32, no-bcmath
2060Syaroslav@ivinco.com	$p = max(0, strlen($v) - 13);
2070Syaroslav@ivinco.com	$lo = (float)substr($v, $p);
2080Syaroslav@ivinco.com	$hi = (float)substr($v, 0, $p);
2090Syaroslav@ivinco.com
2100Syaroslav@ivinco.com	$m = $lo + $hi * 1316134912.0;
2110Syaroslav@ivinco.com	$q = floor($m / 4294967296.0);
2120Syaroslav@ivinco.com	$l = $m - ($q * 4294967296.0);
2130Syaroslav@ivinco.com	$h = $hi * 2328.0 + $q;
2140Syaroslav@ivinco.com
2150Syaroslav@ivinco.com	return pack("NN", $h, $l);
2160Syaroslav@ivinco.com}
2170Syaroslav@ivinco.com
2180Syaroslav@ivinco.com// unpack 64-bit unsigned
2190Syaroslav@ivinco.comfunction sphUnpackU64($v)
2200Syaroslav@ivinco.com{
2210Syaroslav@ivinco.com	list($hi, $lo) = array_values(unpack("N*N*", $v));
2220Syaroslav@ivinco.com
2230Syaroslav@ivinco.com	if (PHP_INT_SIZE >= 8) {
2240Syaroslav@ivinco.com		if ($hi < 0) $hi += (1 << 32); // because php 5.2.2 to 5.2.5 is totally fucked up again
2250Syaroslav@ivinco.com		if ($lo < 0) $lo += (1 << 32);
2260Syaroslav@ivinco.com
2270Syaroslav@ivinco.com		// x64, int
2280Syaroslav@ivinco.com		if ($hi <= 2147483647)
2290Syaroslav@ivinco.com			return ($hi << 32) + $lo;
2300Syaroslav@ivinco.com
2310Syaroslav@ivinco.com		// x64, bcmath
2320Syaroslav@ivinco.com		if (function_exists("bcmul"))
2330Syaroslav@ivinco.com			return bcadd($lo, bcmul($hi, "4294967296"));
2340Syaroslav@ivinco.com
2350Syaroslav@ivinco.com		// x64, no-bcmath
2360Syaroslav@ivinco.com		$C = 100000;
2370Syaroslav@ivinco.com		$h = ((int)($hi / $C) << 32) + (int)($lo / $C);
2380Syaroslav@ivinco.com		$l = (($hi % $C) << 32) + ($lo % $C);
2390Syaroslav@ivinco.com		if ($l > $C) {
2400Syaroslav@ivinco.com			$h += (int)($l / $C);
2410Syaroslav@ivinco.com			$l  = $l % $C;
2420Syaroslav@ivinco.com		}
2430Syaroslav@ivinco.com
2440Syaroslav@ivinco.com		if ($h == 0)
2450Syaroslav@ivinco.com			return $l;
2460Syaroslav@ivinco.com		return sprintf("%d%05d", $h, $l);
2470Syaroslav@ivinco.com	}
2480Syaroslav@ivinco.com
2490Syaroslav@ivinco.com	// x32, int
2500Syaroslav@ivinco.com	if ($hi == 0) {
2510Syaroslav@ivinco.com		if ($lo > 0)
2520Syaroslav@ivinco.com			return $lo;
2530Syaroslav@ivinco.com		return sprintf("%u", $lo);
2540Syaroslav@ivinco.com	}
2550Syaroslav@ivinco.com
2560Syaroslav@ivinco.com	$hi = sprintf("%u", $hi);
2570Syaroslav@ivinco.com	$lo = sprintf("%u", $lo);
2580Syaroslav@ivinco.com
2590Syaroslav@ivinco.com	// x32, bcmath
2600Syaroslav@ivinco.com	if (function_exists("bcmul"))
2610Syaroslav@ivinco.com		return bcadd($lo, bcmul($hi, "4294967296"));
2620Syaroslav@ivinco.com
2630Syaroslav@ivinco.com	// x32, no-bcmath
2640Syaroslav@ivinco.com	$hi = (float)$hi;
2650Syaroslav@ivinco.com	$lo = (float)$lo;
2660Syaroslav@ivinco.com
2670Syaroslav@ivinco.com	$q = floor($hi / 10000000.0);
2680Syaroslav@ivinco.com	$r = $hi - $q * 10000000.0;
2690Syaroslav@ivinco.com	$m = $lo + $r * 4967296.0;
2700Syaroslav@ivinco.com	$mq = floor($m / 10000000.0);
2710Syaroslav@ivinco.com	$l = $m - $mq * 10000000.0;
2720Syaroslav@ivinco.com	$h = $q * 4294967296.0 + $r * 429.0 + $mq;
2730Syaroslav@ivinco.com
2740Syaroslav@ivinco.com	$h = sprintf("%.0f", $h);
2750Syaroslav@ivinco.com	$l = sprintf("%07.0f", $l);
2760Syaroslav@ivinco.com	if ($h == "0")
2770Syaroslav@ivinco.com		return sprintf("%.0f", (float)$l);
2780Syaroslav@ivinco.com	return $h . $l;
2790Syaroslav@ivinco.com}
2800Syaroslav@ivinco.com
2810Syaroslav@ivinco.com// unpack 64-bit signed
2820Syaroslav@ivinco.comfunction sphUnpackI64($v)
2830Syaroslav@ivinco.com{
2840Syaroslav@ivinco.com	list($hi, $lo) = array_values(unpack("N*N*", $v));
2850Syaroslav@ivinco.com
2860Syaroslav@ivinco.com	// x64
2870Syaroslav@ivinco.com	if (PHP_INT_SIZE >= 8) {
2880Syaroslav@ivinco.com		if ($hi < 0) $hi += (1 << 32); // because php 5.2.2 to 5.2.5 is totally fucked up again
2890Syaroslav@ivinco.com		if ($lo < 0) $lo += (1 << 32);
2900Syaroslav@ivinco.com
2910Syaroslav@ivinco.com		return ($hi << 32) + $lo;
2920Syaroslav@ivinco.com	}
2930Syaroslav@ivinco.com
2940Syaroslav@ivinco.com	// x32, int
2950Syaroslav@ivinco.com	if ($hi == 0) {
2960Syaroslav@ivinco.com		if ($lo > 0)
2970Syaroslav@ivinco.com			return $lo;
2980Syaroslav@ivinco.com		return sprintf("%u", $lo);
2990Syaroslav@ivinco.com	}
3000Syaroslav@ivinco.com	// x32, int
3010Syaroslav@ivinco.com	elseif ($hi == -1) {
3020Syaroslav@ivinco.com		if ($lo < 0)
3030Syaroslav@ivinco.com			return $lo;
3040Syaroslav@ivinco.com		return sprintf("%.0f", $lo - 4294967296.0);
3050Syaroslav@ivinco.com	}
3060Syaroslav@ivinco.com
3070Syaroslav@ivinco.com	$neg = "";
3080Syaroslav@ivinco.com	$c = 0;
3090Syaroslav@ivinco.com	if ($hi < 0) {
3100Syaroslav@ivinco.com		$hi = ~$hi;
3110Syaroslav@ivinco.com		$lo = ~$lo;
3120Syaroslav@ivinco.com		$c = 1;
3130Syaroslav@ivinco.com		$neg = "-";
3140Syaroslav@ivinco.com	}
3150Syaroslav@ivinco.com
3160Syaroslav@ivinco.com	$hi = sprintf("%u", $hi);
3170Syaroslav@ivinco.com	$lo = sprintf("%u", $lo);
3180Syaroslav@ivinco.com
3190Syaroslav@ivinco.com	// x32, bcmath
3200Syaroslav@ivinco.com	if (function_exists("bcmul"))
3210Syaroslav@ivinco.com		return $neg . bcadd(bcadd($lo, bcmul($hi, "4294967296")), $c);
3220Syaroslav@ivinco.com
3230Syaroslav@ivinco.com	// x32, no-bcmath
3240Syaroslav@ivinco.com	$hi = (float)$hi;
3250Syaroslav@ivinco.com	$lo = (float)$lo;
3260Syaroslav@ivinco.com
3270Syaroslav@ivinco.com	$q = floor($hi / 10000000.0);
3280Syaroslav@ivinco.com	$r = $hi - $q * 10000000.0;
3290Syaroslav@ivinco.com	$m = $lo + $r * 4967296.0;
3300Syaroslav@ivinco.com	$mq = floor($m / 10000000.0);
3310Syaroslav@ivinco.com	$l = $m - $mq * 10000000.0 + $c;
3320Syaroslav@ivinco.com	$h = $q * 4294967296.0 + $r * 429.0 + $mq;
3330Syaroslav@ivinco.com	if ($l == 10000000) {
3340Syaroslav@ivinco.com		$l = 0;
3350Syaroslav@ivinco.com		$h += 1;
3360Syaroslav@ivinco.com	}
3370Syaroslav@ivinco.com
3380Syaroslav@ivinco.com	$h = sprintf("%.0f", $h);
3390Syaroslav@ivinco.com	$l = sprintf("%07.0f", $l);
3400Syaroslav@ivinco.com	if ($h == "0")
3410Syaroslav@ivinco.com		return $neg . sprintf("%.0f", (float)$l);
3420Syaroslav@ivinco.com	return $neg . $h . $l;
3430Syaroslav@ivinco.com}
3440Syaroslav@ivinco.com
3450Syaroslav@ivinco.com
3460Syaroslav@ivinco.comfunction sphFixUint($value)
3470Syaroslav@ivinco.com{
3480Syaroslav@ivinco.com	if (PHP_INT_SIZE >= 8) {
3490Syaroslav@ivinco.com		// x64 route, workaround broken unpack() in 5.2.2+
3500Syaroslav@ivinco.com		if ($value < 0) $value += (1 << 32);
3510Syaroslav@ivinco.com		return $value;
3520Syaroslav@ivinco.com	} else {
3530Syaroslav@ivinco.com		// x32 route, workaround php signed/unsigned braindamage
3540Syaroslav@ivinco.com		return sprintf("%u", $value);
3550Syaroslav@ivinco.com	}
3560Syaroslav@ivinco.com}
3570Syaroslav@ivinco.com
3580Syaroslav@ivinco.comif (!class_exists('SphinxClient')) {
3590Syaroslav@ivinco.com	/// sphinx searchd client class
3600Syaroslav@ivinco.com	class SphinxClient
3610Syaroslav@ivinco.com	{
3620Syaroslav@ivinco.com		var $_host;			///< searchd host (default is "localhost")
3630Syaroslav@ivinco.com		var $_port;			///< searchd port (default is 9312)
3640Syaroslav@ivinco.com		var $_offset;		///< how many records to seek from result-set start (default is 0)
3650Syaroslav@ivinco.com		var $_limit;		///< how many records to return from result-set starting at offset (default is 20)
3660Syaroslav@ivinco.com		var $_mode;			///< query matching mode (default is SPH_MATCH_ALL)
3670Syaroslav@ivinco.com		var $_weights;		///< per-field weights (default is 1 for all fields)
3680Syaroslav@ivinco.com		var $_sort;			///< match sorting mode (default is SPH_SORT_RELEVANCE)
3690Syaroslav@ivinco.com		var $_sortby;		///< attribute to sort by (defualt is "")
3700Syaroslav@ivinco.com		var $_min_id;		///< min ID to match (default is 0, which means no limit)
3710Syaroslav@ivinco.com		var $_max_id;		///< max ID to match (default is 0, which means no limit)
3720Syaroslav@ivinco.com		var $_filters;		///< search filters
3730Syaroslav@ivinco.com		var $_groupby;		///< group-by attribute name
3740Syaroslav@ivinco.com		var $_groupfunc;	///< group-by function (to pre-process group-by attribute value with)
3750Syaroslav@ivinco.com		var $_groupsort;	///< group-by sorting clause (to sort groups in result set with)
376*133Sandrey		var $_groupdistinct; ///< group-by count-distinct attribute
3770Syaroslav@ivinco.com		var $_maxmatches;	///< max matches to retrieve
3780Syaroslav@ivinco.com		var $_cutoff;		///< cutoff to stop searching at (default is 0)
3790Syaroslav@ivinco.com		var $_retrycount;	///< distributed retries count
3800Syaroslav@ivinco.com		var $_retrydelay;	///< distributed retries delay
3810Syaroslav@ivinco.com		var $_anchor;		///< geographical anchor point
3820Syaroslav@ivinco.com		var $_indexweights;	///< per-index weights
3830Syaroslav@ivinco.com		var $_ranker;		///< ranking mode (default is SPH_RANK_PROXIMITY_BM25)
3840Syaroslav@ivinco.com		var $_maxquerytime;	///< max query time, milliseconds (default is 0, do not limit)
3850Syaroslav@ivinco.com		var $_fieldweights;	///< per-field-name weights
3860Syaroslav@ivinco.com		var $_overrides;	///< per-query attribute values overrides
3870Syaroslav@ivinco.com		var $_select;		///< select-list (attributes or expressions, with optional aliases)
3880Syaroslav@ivinco.com
3890Syaroslav@ivinco.com		var $_error;		///< last error message
3900Syaroslav@ivinco.com		var $_warning;		///< last warning message
3910Syaroslav@ivinco.com		var $_connerror;		///< connection error vs remote error flag
3920Syaroslav@ivinco.com
3930Syaroslav@ivinco.com		var $_reqs;			///< requests array for multi-query
3940Syaroslav@ivinco.com		var $_mbenc;		///< stored mbstring encoding
3950Syaroslav@ivinco.com		var $_arrayresult;	///< whether $result["matches"] should be a hash or an array
3960Syaroslav@ivinco.com		var $_timeout;		///< connect timeout
3970Syaroslav@ivinco.com
3980Syaroslav@ivinco.com		/////////////////////////////////////////////////////////////////////////////
3990Syaroslav@ivinco.com		// common stuff
4000Syaroslav@ivinco.com		/////////////////////////////////////////////////////////////////////////////
4010Syaroslav@ivinco.com
4020Syaroslav@ivinco.com		/// create a new client object and fill defaults
4030Syaroslav@ivinco.com		function SphinxClient()
4040Syaroslav@ivinco.com		{
4050Syaroslav@ivinco.com			// per-client-object settings
4060Syaroslav@ivinco.com			$this->_host		= "localhost";
4070Syaroslav@ivinco.com			$this->_port		= 9312;
4080Syaroslav@ivinco.com			$this->_path		= false;
4090Syaroslav@ivinco.com			$this->_socket		= false;
4100Syaroslav@ivinco.com
4110Syaroslav@ivinco.com			// per-query settings
4120Syaroslav@ivinco.com			$this->_offset		= 0;
4130Syaroslav@ivinco.com			$this->_limit		= 20;
4140Syaroslav@ivinco.com			$this->_mode		= SPH_MATCH_ALL;
4150Syaroslav@ivinco.com			$this->_weights		= array();
4160Syaroslav@ivinco.com			$this->_sort		= SPH_SORT_RELEVANCE;
4170Syaroslav@ivinco.com			$this->_sortby		= "";
4180Syaroslav@ivinco.com			$this->_min_id		= 0;
4190Syaroslav@ivinco.com			$this->_max_id		= 0;
4200Syaroslav@ivinco.com			$this->_filters		= array();
4210Syaroslav@ivinco.com			$this->_groupby		= "";
4220Syaroslav@ivinco.com			$this->_groupfunc	= SPH_GROUPBY_DAY;
4230Syaroslav@ivinco.com			$this->_groupsort	= "@group desc";
4240Syaroslav@ivinco.com			$this->_groupdistinct = "";
4250Syaroslav@ivinco.com			$this->_maxmatches	= 1000;
4260Syaroslav@ivinco.com			$this->_cutoff		= 0;
4270Syaroslav@ivinco.com			$this->_retrycount	= 0;
4280Syaroslav@ivinco.com			$this->_retrydelay	= 0;
4290Syaroslav@ivinco.com			$this->_anchor		= array();
4300Syaroslav@ivinco.com			$this->_indexweights = array();
4310Syaroslav@ivinco.com			$this->_ranker		= SPH_RANK_PROXIMITY_BM25;
4320Syaroslav@ivinco.com			$this->_maxquerytime = 0;
4330Syaroslav@ivinco.com			$this->_fieldweights = array();
4340Syaroslav@ivinco.com			$this->_overrides 	= array();
4350Syaroslav@ivinco.com			$this->_select		= "*";
4360Syaroslav@ivinco.com
4370Syaroslav@ivinco.com			$this->_error		= ""; // per-reply fields (for single-query case)
4380Syaroslav@ivinco.com			$this->_warning		= "";
4390Syaroslav@ivinco.com			$this->_connerror	= false;
4400Syaroslav@ivinco.com
4410Syaroslav@ivinco.com			$this->_reqs		= array();	// requests storage (for multi-query case)
4420Syaroslav@ivinco.com			$this->_mbenc		= "";
4430Syaroslav@ivinco.com			$this->_arrayresult	= false;
4440Syaroslav@ivinco.com			$this->_timeout		= 0;
4450Syaroslav@ivinco.com		}
4460Syaroslav@ivinco.com
4470Syaroslav@ivinco.com		function __destruct()
4480Syaroslav@ivinco.com		{
4490Syaroslav@ivinco.com			if ($this->_socket !== false)
4500Syaroslav@ivinco.com				fclose($this->_socket);
4510Syaroslav@ivinco.com		}
4520Syaroslav@ivinco.com
4530Syaroslav@ivinco.com		/// get last error message (string)
4540Syaroslav@ivinco.com		function GetLastError()
4550Syaroslav@ivinco.com		{
4560Syaroslav@ivinco.com			return $this->_error;
4570Syaroslav@ivinco.com		}
4580Syaroslav@ivinco.com
4590Syaroslav@ivinco.com		/// get last warning message (string)
4600Syaroslav@ivinco.com		function GetLastWarning()
4610Syaroslav@ivinco.com		{
4620Syaroslav@ivinco.com			return $this->_warning;
4630Syaroslav@ivinco.com		}
4640Syaroslav@ivinco.com
4650Syaroslav@ivinco.com		/// get last error flag (to tell network connection errors from searchd errors or broken responses)
4660Syaroslav@ivinco.com		function IsConnectError()
4670Syaroslav@ivinco.com		{
4680Syaroslav@ivinco.com			return $this->_connerror;
4690Syaroslav@ivinco.com		}
4700Syaroslav@ivinco.com
4710Syaroslav@ivinco.com		/// set searchd host name (string) and port (integer)
4720Syaroslav@ivinco.com		function SetServer($host, $port = 0)
4730Syaroslav@ivinco.com		{
4740Syaroslav@ivinco.com			assert(is_string($host));
4750Syaroslav@ivinco.com			if ($host[0] == '/') {
4760Syaroslav@ivinco.com				$this->_path = 'unix://' . $host;
4770Syaroslav@ivinco.com				return;
4780Syaroslav@ivinco.com			}
4790Syaroslav@ivinco.com			if (substr($host, 0, 7) == "unix://") {
4800Syaroslav@ivinco.com				$this->_path = $host;
4810Syaroslav@ivinco.com				return;
4820Syaroslav@ivinco.com			}
4830Syaroslav@ivinco.com
4840Syaroslav@ivinco.com			assert(is_int($port));
4850Syaroslav@ivinco.com			$this->_host = $host;
4860Syaroslav@ivinco.com			$this->_port = $port;
4870Syaroslav@ivinco.com			$this->_path = '';
4880Syaroslav@ivinco.com		}
4890Syaroslav@ivinco.com
4900Syaroslav@ivinco.com		/// set server connection timeout (0 to remove)
4910Syaroslav@ivinco.com		function SetConnectTimeout($timeout)
4920Syaroslav@ivinco.com		{
4930Syaroslav@ivinco.com			assert(is_numeric($timeout));
4940Syaroslav@ivinco.com			$this->_timeout = $timeout;
4950Syaroslav@ivinco.com		}
4960Syaroslav@ivinco.com
4970Syaroslav@ivinco.com
4980Syaroslav@ivinco.com		function _Send($handle, $data, $length)
4990Syaroslav@ivinco.com		{
5000Syaroslav@ivinco.com			if (feof($handle) || fwrite($handle, $data, $length) !== $length) {
5010Syaroslav@ivinco.com				$this->_error = 'connection unexpectedly closed (timed out?)';
5020Syaroslav@ivinco.com				$this->_connerror = true;
5030Syaroslav@ivinco.com				return false;
5040Syaroslav@ivinco.com			}
5050Syaroslav@ivinco.com			return true;
5060Syaroslav@ivinco.com		}
5070Syaroslav@ivinco.com
5080Syaroslav@ivinco.com		/////////////////////////////////////////////////////////////////////////////
5090Syaroslav@ivinco.com
5100Syaroslav@ivinco.com		/// enter mbstring workaround mode
5110Syaroslav@ivinco.com		function _MBPush()
5120Syaroslav@ivinco.com		{
5130Syaroslav@ivinco.com			$this->_mbenc = "";
5140Syaroslav@ivinco.com			if (ini_get("mbstring.func_overload") & 2) {
5150Syaroslav@ivinco.com				$this->_mbenc = mb_internal_encoding();
5160Syaroslav@ivinco.com				mb_internal_encoding("latin1");
5170Syaroslav@ivinco.com			}
5180Syaroslav@ivinco.com		}
5190Syaroslav@ivinco.com
5200Syaroslav@ivinco.com		/// leave mbstring workaround mode
5210Syaroslav@ivinco.com		function _MBPop()
5220Syaroslav@ivinco.com		{
5230Syaroslav@ivinco.com			if ($this->_mbenc)
5240Syaroslav@ivinco.com				mb_internal_encoding($this->_mbenc);
5250Syaroslav@ivinco.com		}
5260Syaroslav@ivinco.com
5270Syaroslav@ivinco.com		/// connect to searchd server
5280Syaroslav@ivinco.com		function _Connect()
5290Syaroslav@ivinco.com		{
5300Syaroslav@ivinco.com			if ($this->_socket !== false) {
5310Syaroslav@ivinco.com				// we are in persistent connection mode, so we have a socket
5320Syaroslav@ivinco.com				// however, need to check whether it's still alive
5330Syaroslav@ivinco.com				if (!@feof($this->_socket))
5340Syaroslav@ivinco.com					return $this->_socket;
5350Syaroslav@ivinco.com
5360Syaroslav@ivinco.com				// force reopen
5370Syaroslav@ivinco.com				$this->_socket = false;
5380Syaroslav@ivinco.com			}
5390Syaroslav@ivinco.com
5400Syaroslav@ivinco.com			$errno = 0;
5410Syaroslav@ivinco.com			$errstr = "";
5420Syaroslav@ivinco.com			$this->_connerror = false;
5430Syaroslav@ivinco.com
5440Syaroslav@ivinco.com			if ($this->_path) {
5450Syaroslav@ivinco.com				$host = $this->_path;
5460Syaroslav@ivinco.com				$port = 0;
5470Syaroslav@ivinco.com			} else {
5480Syaroslav@ivinco.com				$host = $this->_host;
5490Syaroslav@ivinco.com				$port = $this->_port;
5500Syaroslav@ivinco.com			}
5510Syaroslav@ivinco.com
5520Syaroslav@ivinco.com			if ($this->_timeout <= 0)
5530Syaroslav@ivinco.com				$fp = @fsockopen($host, $port, $errno, $errstr);
5540Syaroslav@ivinco.com			else
5550Syaroslav@ivinco.com				$fp = @fsockopen($host, $port, $errno, $errstr, $this->_timeout);
5560Syaroslav@ivinco.com
5570Syaroslav@ivinco.com			if (!$fp) {
5580Syaroslav@ivinco.com				if ($this->_path)
5590Syaroslav@ivinco.com					$location = $this->_path;
5600Syaroslav@ivinco.com				else
5610Syaroslav@ivinco.com					$location = "{$this->_host}:{$this->_port}";
5620Syaroslav@ivinco.com
5630Syaroslav@ivinco.com				$errstr = trim($errstr);
5640Syaroslav@ivinco.com				$this->_error = "connection to $location failed (errno=$errno, msg=$errstr)";
5650Syaroslav@ivinco.com				$this->_connerror = true;
5660Syaroslav@ivinco.com				return false;
5670Syaroslav@ivinco.com			}
5680Syaroslav@ivinco.com
5690Syaroslav@ivinco.com			// send my version
5700Syaroslav@ivinco.com			// this is a subtle part. we must do it before (!) reading back from searchd.
5710Syaroslav@ivinco.com			// because otherwise under some conditions (reported on FreeBSD for instance)
5720Syaroslav@ivinco.com			// TCP stack could throttle write-write-read pattern because of Nagle.
5730Syaroslav@ivinco.com			if (!$this->_Send($fp, pack("N", 1), 4)) {
5740Syaroslav@ivinco.com				fclose($fp);
5750Syaroslav@ivinco.com				$this->_error = "failed to send client protocol version";
5760Syaroslav@ivinco.com				return false;
5770Syaroslav@ivinco.com			}
5780Syaroslav@ivinco.com
5790Syaroslav@ivinco.com			// check version
5800Syaroslav@ivinco.com			list(, $v) = unpack("N*", fread($fp, 4));
5810Syaroslav@ivinco.com			$v = (int)$v;
5820Syaroslav@ivinco.com			if ($v < 1) {
5830Syaroslav@ivinco.com				fclose($fp);
5840Syaroslav@ivinco.com				$this->_error = "expected searchd protocol version 1+, got version '$v'";
5850Syaroslav@ivinco.com				return false;
5860Syaroslav@ivinco.com			}
5870Syaroslav@ivinco.com
5880Syaroslav@ivinco.com			return $fp;
5890Syaroslav@ivinco.com		}
5900Syaroslav@ivinco.com
5910Syaroslav@ivinco.com		/// get and check response packet from searchd server
5920Syaroslav@ivinco.com		function _GetResponse($fp, $client_ver)
5930Syaroslav@ivinco.com		{
5940Syaroslav@ivinco.com			$response = "";
5950Syaroslav@ivinco.com			$len = 0;
5960Syaroslav@ivinco.com
5970Syaroslav@ivinco.com			$header = fread($fp, 8);
5980Syaroslav@ivinco.com			if (strlen($header) == 8) {
5990Syaroslav@ivinco.com				list($status, $ver, $len) = array_values(unpack("n2a/Nb", $header));
6000Syaroslav@ivinco.com				$left = $len;
6010Syaroslav@ivinco.com				while ($left > 0 && !feof($fp)) {
6020Syaroslav@ivinco.com					$chunk = fread($fp, $left);
6030Syaroslav@ivinco.com					if ($chunk) {
6040Syaroslav@ivinco.com						$response .= $chunk;
6050Syaroslav@ivinco.com						$left -= strlen($chunk);
6060Syaroslav@ivinco.com					}
6070Syaroslav@ivinco.com				}
6080Syaroslav@ivinco.com			}
6090Syaroslav@ivinco.com			if ($this->_socket === false)
6100Syaroslav@ivinco.com				fclose($fp);
6110Syaroslav@ivinco.com
6120Syaroslav@ivinco.com			// check response
6130Syaroslav@ivinco.com			$read = strlen($response);
6140Syaroslav@ivinco.com			if (!$response || $read != $len) {
6150Syaroslav@ivinco.com				$this->_error = $len
6160Syaroslav@ivinco.com					? "failed to read searchd response (status=$status, ver=$ver, len=$len, read=$read)"
6170Syaroslav@ivinco.com					: "received zero-sized searchd response";
6180Syaroslav@ivinco.com				return false;
6190Syaroslav@ivinco.com			}
6200Syaroslav@ivinco.com
6210Syaroslav@ivinco.com			// check status
6220Syaroslav@ivinco.com			if ($status == SEARCHD_WARNING) {
6230Syaroslav@ivinco.com				list(, $wlen) = unpack("N*", substr($response, 0, 4));
6240Syaroslav@ivinco.com				$this->_warning = substr($response, 4, $wlen);
6250Syaroslav@ivinco.com				return substr($response, 4 + $wlen);
6260Syaroslav@ivinco.com			}
6270Syaroslav@ivinco.com			if ($status == SEARCHD_ERROR) {
6280Syaroslav@ivinco.com				$this->_error = "searchd error: " . substr($response, 4);
6290Syaroslav@ivinco.com				return false;
6300Syaroslav@ivinco.com			}
6310Syaroslav@ivinco.com			if ($status == SEARCHD_RETRY) {
6320Syaroslav@ivinco.com				$this->_error = "temporary searchd error: " . substr($response, 4);
6330Syaroslav@ivinco.com				return false;
6340Syaroslav@ivinco.com			}
6350Syaroslav@ivinco.com			if ($status != SEARCHD_OK) {
6360Syaroslav@ivinco.com				$this->_error = "unknown status code '$status'";
6370Syaroslav@ivinco.com				return false;
6380Syaroslav@ivinco.com			}
6390Syaroslav@ivinco.com
6400Syaroslav@ivinco.com			// check version
6410Syaroslav@ivinco.com			if ($ver < $client_ver) {
6420Syaroslav@ivinco.com				$this->_warning = sprintf(
6430Syaroslav@ivinco.com					"searchd command v.%d.%d older than client's v.%d.%d, some options might not work",
6440Syaroslav@ivinco.com					$ver >> 8,
6450Syaroslav@ivinco.com					$ver & 0xff,
6460Syaroslav@ivinco.com					$client_ver >> 8,
6470Syaroslav@ivinco.com					$client_ver & 0xff
6480Syaroslav@ivinco.com				);
6490Syaroslav@ivinco.com			}
6500Syaroslav@ivinco.com
6510Syaroslav@ivinco.com			return $response;
6520Syaroslav@ivinco.com		}
6530Syaroslav@ivinco.com
6540Syaroslav@ivinco.com		/////////////////////////////////////////////////////////////////////////////
6550Syaroslav@ivinco.com		// searching
6560Syaroslav@ivinco.com		/////////////////////////////////////////////////////////////////////////////
6570Syaroslav@ivinco.com
6580Syaroslav@ivinco.com		/// set offset and count into result set,
6590Syaroslav@ivinco.com		/// and optionally set max-matches and cutoff limits
6600Syaroslav@ivinco.com		function SetLimits($offset, $limit, $max = 0, $cutoff = 0)
6610Syaroslav@ivinco.com		{
6620Syaroslav@ivinco.com			assert(is_int($offset));
6630Syaroslav@ivinco.com			assert(is_int($limit));
6640Syaroslav@ivinco.com			assert($offset >= 0);
6650Syaroslav@ivinco.com			assert($limit > 0);
6660Syaroslav@ivinco.com			assert($max >= 0);
6670Syaroslav@ivinco.com			$this->_offset = $offset;
6680Syaroslav@ivinco.com			$this->_limit = $limit;
6690Syaroslav@ivinco.com			if ($max > 0)
6700Syaroslav@ivinco.com				$this->_maxmatches = $max;
6710Syaroslav@ivinco.com			if ($cutoff > 0)
6720Syaroslav@ivinco.com				$this->_cutoff = $cutoff;
6730Syaroslav@ivinco.com		}
6740Syaroslav@ivinco.com
6750Syaroslav@ivinco.com		/// set maximum query time, in milliseconds, per-index
6760Syaroslav@ivinco.com		/// integer, 0 means "do not limit"
6770Syaroslav@ivinco.com		function SetMaxQueryTime($max)
6780Syaroslav@ivinco.com		{
6790Syaroslav@ivinco.com			assert(is_int($max));
6800Syaroslav@ivinco.com			assert($max >= 0);
6810Syaroslav@ivinco.com			$this->_maxquerytime = $max;
6820Syaroslav@ivinco.com		}
6830Syaroslav@ivinco.com
6840Syaroslav@ivinco.com		/// set matching mode
6850Syaroslav@ivinco.com		function SetMatchMode($mode)
6860Syaroslav@ivinco.com		{
6870Syaroslav@ivinco.com			assert($mode == SPH_MATCH_ALL
6880Syaroslav@ivinco.com				|| $mode == SPH_MATCH_ANY
6890Syaroslav@ivinco.com				|| $mode == SPH_MATCH_PHRASE
6900Syaroslav@ivinco.com				|| $mode == SPH_MATCH_BOOLEAN
6910Syaroslav@ivinco.com				|| $mode == SPH_MATCH_EXTENDED
6920Syaroslav@ivinco.com				|| $mode == SPH_MATCH_FULLSCAN
6930Syaroslav@ivinco.com				|| $mode == SPH_MATCH_EXTENDED2);
6940Syaroslav@ivinco.com			$this->_mode = $mode;
6950Syaroslav@ivinco.com		}
6960Syaroslav@ivinco.com
6970Syaroslav@ivinco.com		/// set ranking mode
6980Syaroslav@ivinco.com		function SetRankingMode($ranker)
6990Syaroslav@ivinco.com		{
7000Syaroslav@ivinco.com			assert($ranker == SPH_RANK_PROXIMITY_BM25
7010Syaroslav@ivinco.com				|| $ranker == SPH_RANK_BM25
7020Syaroslav@ivinco.com				|| $ranker == SPH_RANK_NONE
7030Syaroslav@ivinco.com				|| $ranker == SPH_RANK_WORDCOUNT
7040Syaroslav@ivinco.com				|| $ranker == SPH_RANK_PROXIMITY);
7050Syaroslav@ivinco.com			$this->_ranker = $ranker;
7060Syaroslav@ivinco.com		}
7070Syaroslav@ivinco.com
7080Syaroslav@ivinco.com		/// set matches sorting mode
7090Syaroslav@ivinco.com		function SetSortMode($mode, $sortby = "")
7100Syaroslav@ivinco.com		{
7110Syaroslav@ivinco.com			assert(
7120Syaroslav@ivinco.com				$mode == SPH_SORT_RELEVANCE ||
7130Syaroslav@ivinco.com					$mode == SPH_SORT_ATTR_DESC ||
7140Syaroslav@ivinco.com					$mode == SPH_SORT_ATTR_ASC ||
7150Syaroslav@ivinco.com					$mode == SPH_SORT_TIME_SEGMENTS ||
7160Syaroslav@ivinco.com					$mode == SPH_SORT_EXTENDED ||
7170Syaroslav@ivinco.com					$mode == SPH_SORT_EXPR
7180Syaroslav@ivinco.com			);
7190Syaroslav@ivinco.com			assert(is_string($sortby));
7200Syaroslav@ivinco.com			assert($mode == SPH_SORT_RELEVANCE || strlen($sortby) > 0);
7210Syaroslav@ivinco.com
7220Syaroslav@ivinco.com			$this->_sort = $mode;
7230Syaroslav@ivinco.com			$this->_sortby = $sortby;
7240Syaroslav@ivinco.com		}
7250Syaroslav@ivinco.com
7260Syaroslav@ivinco.com		/// bind per-field weights by order
7270Syaroslav@ivinco.com		/// DEPRECATED; use SetFieldWeights() instead
7280Syaroslav@ivinco.com		function SetWeights($weights)
7290Syaroslav@ivinco.com		{
7300Syaroslav@ivinco.com			assert(is_array($weights));
7310Syaroslav@ivinco.com			foreach ($weights as $weight)
7320Syaroslav@ivinco.com				assert(is_int($weight));
7330Syaroslav@ivinco.com
7340Syaroslav@ivinco.com			$this->_weights = $weights;
7350Syaroslav@ivinco.com		}
7360Syaroslav@ivinco.com
7370Syaroslav@ivinco.com		/// bind per-field weights by name
7380Syaroslav@ivinco.com		function SetFieldWeights($weights)
7390Syaroslav@ivinco.com		{
7400Syaroslav@ivinco.com			assert(is_array($weights));
7410Syaroslav@ivinco.com			foreach ($weights as $name => $weight) {
7420Syaroslav@ivinco.com				assert(is_string($name));
7430Syaroslav@ivinco.com				assert(is_int($weight));
7440Syaroslav@ivinco.com			}
7450Syaroslav@ivinco.com			$this->_fieldweights = $weights;
7460Syaroslav@ivinco.com		}
7470Syaroslav@ivinco.com
7480Syaroslav@ivinco.com		/// bind per-index weights by name
7490Syaroslav@ivinco.com		function SetIndexWeights($weights)
7500Syaroslav@ivinco.com		{
7510Syaroslav@ivinco.com			assert(is_array($weights));
7520Syaroslav@ivinco.com			foreach ($weights as $index => $weight) {
7530Syaroslav@ivinco.com				assert(is_string($index));
7540Syaroslav@ivinco.com				assert(is_int($weight));
7550Syaroslav@ivinco.com			}
7560Syaroslav@ivinco.com			$this->_indexweights = $weights;
7570Syaroslav@ivinco.com		}
7580Syaroslav@ivinco.com
7590Syaroslav@ivinco.com		/// set IDs range to match
7600Syaroslav@ivinco.com		/// only match records if document ID is beetwen $min and $max (inclusive)
7610Syaroslav@ivinco.com		function SetIDRange($min, $max)
7620Syaroslav@ivinco.com		{
7630Syaroslav@ivinco.com			assert(is_numeric($min));
7640Syaroslav@ivinco.com			assert(is_numeric($max));
7650Syaroslav@ivinco.com			assert($min <= $max);
7660Syaroslav@ivinco.com			$this->_min_id = $min;
7670Syaroslav@ivinco.com			$this->_max_id = $max;
7680Syaroslav@ivinco.com		}
7690Syaroslav@ivinco.com
7700Syaroslav@ivinco.com		/// set values set filter
7710Syaroslav@ivinco.com		/// only match records where $attribute value is in given set
7720Syaroslav@ivinco.com		function SetFilter($attribute, $values, $exclude = false)
7730Syaroslav@ivinco.com		{
7740Syaroslav@ivinco.com			assert(is_string($attribute));
7750Syaroslav@ivinco.com			assert(is_array($values));
7760Syaroslav@ivinco.com			assert(count($values));
7770Syaroslav@ivinco.com
7780Syaroslav@ivinco.com			if (is_array($values) && count($values)) {
7790Syaroslav@ivinco.com				foreach ($values as $value)
7800Syaroslav@ivinco.com					assert(is_numeric($value));
7810Syaroslav@ivinco.com
7820Syaroslav@ivinco.com				$this->_filters[] = array("type" => SPH_FILTER_VALUES, "attr" => $attribute, "exclude" => $exclude, "values" => $values);
7830Syaroslav@ivinco.com			}
7840Syaroslav@ivinco.com		}
7850Syaroslav@ivinco.com
7860Syaroslav@ivinco.com		/// set range filter
7870Syaroslav@ivinco.com		/// only match records if $attribute value is beetwen $min and $max (inclusive)
7880Syaroslav@ivinco.com		function SetFilterRange($attribute, $min, $max, $exclude = false)
7890Syaroslav@ivinco.com		{
7900Syaroslav@ivinco.com			assert(is_string($attribute));
7910Syaroslav@ivinco.com			assert(is_numeric($min));
7920Syaroslav@ivinco.com			assert(is_numeric($max));
7930Syaroslav@ivinco.com			assert($min <= $max);
7940Syaroslav@ivinco.com
7950Syaroslav@ivinco.com			$this->_filters[] = array("type" => SPH_FILTER_RANGE, "attr" => $attribute, "exclude" => $exclude, "min" => $min, "max" => $max);
7960Syaroslav@ivinco.com		}
7970Syaroslav@ivinco.com
7980Syaroslav@ivinco.com		/// set float range filter
7990Syaroslav@ivinco.com		/// only match records if $attribute value is beetwen $min and $max (inclusive)
8000Syaroslav@ivinco.com		function SetFilterFloatRange($attribute, $min, $max, $exclude = false)
8010Syaroslav@ivinco.com		{
8020Syaroslav@ivinco.com			assert(is_string($attribute));
8030Syaroslav@ivinco.com			assert(is_float($min));
8040Syaroslav@ivinco.com			assert(is_float($max));
8050Syaroslav@ivinco.com			assert($min <= $max);
8060Syaroslav@ivinco.com
8070Syaroslav@ivinco.com			$this->_filters[] = array("type" => SPH_FILTER_FLOATRANGE, "attr" => $attribute, "exclude" => $exclude, "min" => $min, "max" => $max);
8080Syaroslav@ivinco.com		}
8090Syaroslav@ivinco.com
8100Syaroslav@ivinco.com		/// setup anchor point for geosphere distance calculations
8110Syaroslav@ivinco.com		/// required to use @geodist in filters and sorting
8120Syaroslav@ivinco.com		/// latitude and longitude must be in radians
8130Syaroslav@ivinco.com		function SetGeoAnchor($attrlat, $attrlong, $lat, $long)
8140Syaroslav@ivinco.com		{
8150Syaroslav@ivinco.com			assert(is_string($attrlat));
8160Syaroslav@ivinco.com			assert(is_string($attrlong));
8170Syaroslav@ivinco.com			assert(is_float($lat));
8180Syaroslav@ivinco.com			assert(is_float($long));
8190Syaroslav@ivinco.com
8200Syaroslav@ivinco.com			$this->_anchor = array("attrlat" => $attrlat, "attrlong" => $attrlong, "lat" => $lat, "long" => $long);
8210Syaroslav@ivinco.com		}
8220Syaroslav@ivinco.com
8230Syaroslav@ivinco.com		/// set grouping attribute and function
8240Syaroslav@ivinco.com		function SetGroupBy($attribute, $func, $groupsort = "@group desc")
8250Syaroslav@ivinco.com		{
8260Syaroslav@ivinco.com			assert(is_string($attribute));
8270Syaroslav@ivinco.com			assert(is_string($groupsort));
8280Syaroslav@ivinco.com			assert($func == SPH_GROUPBY_DAY
8290Syaroslav@ivinco.com				|| $func == SPH_GROUPBY_WEEK
8300Syaroslav@ivinco.com				|| $func == SPH_GROUPBY_MONTH
8310Syaroslav@ivinco.com				|| $func == SPH_GROUPBY_YEAR
8320Syaroslav@ivinco.com				|| $func == SPH_GROUPBY_ATTR
8330Syaroslav@ivinco.com				|| $func == SPH_GROUPBY_ATTRPAIR);
8340Syaroslav@ivinco.com
8350Syaroslav@ivinco.com			$this->_groupby = $attribute;
8360Syaroslav@ivinco.com			$this->_groupfunc = $func;
8370Syaroslav@ivinco.com			$this->_groupsort = $groupsort;
8380Syaroslav@ivinco.com		}
8390Syaroslav@ivinco.com
8400Syaroslav@ivinco.com		/// set count-distinct attribute for group-by queries
8410Syaroslav@ivinco.com		function SetGroupDistinct($attribute)
8420Syaroslav@ivinco.com		{
8430Syaroslav@ivinco.com			assert(is_string($attribute));
8440Syaroslav@ivinco.com			$this->_groupdistinct = $attribute;
8450Syaroslav@ivinco.com		}
8460Syaroslav@ivinco.com
8470Syaroslav@ivinco.com		/// set distributed retries count and delay
8480Syaroslav@ivinco.com		function SetRetries($count, $delay = 0)
8490Syaroslav@ivinco.com		{
8500Syaroslav@ivinco.com			assert(is_int($count) && $count >= 0);
8510Syaroslav@ivinco.com			assert(is_int($delay) && $delay >= 0);
8520Syaroslav@ivinco.com			$this->_retrycount = $count;
8530Syaroslav@ivinco.com			$this->_retrydelay = $delay;
8540Syaroslav@ivinco.com		}
8550Syaroslav@ivinco.com
8560Syaroslav@ivinco.com		/// set result set format (hash or array; hash by default)
8570Syaroslav@ivinco.com		/// PHP specific; needed for group-by-MVA result sets that may contain duplicate IDs
8580Syaroslav@ivinco.com		function SetArrayResult($arrayresult)
8590Syaroslav@ivinco.com		{
8600Syaroslav@ivinco.com			assert(is_bool($arrayresult));
8610Syaroslav@ivinco.com			$this->_arrayresult = $arrayresult;
8620Syaroslav@ivinco.com		}
8630Syaroslav@ivinco.com
8640Syaroslav@ivinco.com		/// set attribute values override
8650Syaroslav@ivinco.com		/// there can be only one override per attribute
8660Syaroslav@ivinco.com		/// $values must be a hash that maps document IDs to attribute values
8670Syaroslav@ivinco.com		function SetOverride($attrname, $attrtype, $values)
8680Syaroslav@ivinco.com		{
8690Syaroslav@ivinco.com			assert(is_string($attrname));
8700Syaroslav@ivinco.com			assert(in_array($attrtype, array(SPH_ATTR_INTEGER, SPH_ATTR_TIMESTAMP, SPH_ATTR_BOOL, SPH_ATTR_FLOAT, SPH_ATTR_BIGINT)));
8710Syaroslav@ivinco.com			assert(is_array($values));
8720Syaroslav@ivinco.com
8730Syaroslav@ivinco.com			$this->_overrides[$attrname] = array("attr" => $attrname, "type" => $attrtype, "values" => $values);
8740Syaroslav@ivinco.com		}
8750Syaroslav@ivinco.com
8760Syaroslav@ivinco.com		/// set select-list (attributes or expressions), SQL-like syntax
8770Syaroslav@ivinco.com		function SetSelect($select)
8780Syaroslav@ivinco.com		{
8790Syaroslav@ivinco.com			assert(is_string($select));
8800Syaroslav@ivinco.com			$this->_select = $select;
8810Syaroslav@ivinco.com		}
8820Syaroslav@ivinco.com
8830Syaroslav@ivinco.com		//////////////////////////////////////////////////////////////////////////////
8840Syaroslav@ivinco.com
8850Syaroslav@ivinco.com		/// clear all filters (for multi-queries)
8860Syaroslav@ivinco.com		function ResetFilters()
8870Syaroslav@ivinco.com		{
8880Syaroslav@ivinco.com			$this->_filters = array();
8890Syaroslav@ivinco.com			$this->_anchor = array();
8900Syaroslav@ivinco.com		}
8910Syaroslav@ivinco.com
8920Syaroslav@ivinco.com		/// clear groupby settings (for multi-queries)
8930Syaroslav@ivinco.com		function ResetGroupBy()
8940Syaroslav@ivinco.com		{
8950Syaroslav@ivinco.com			$this->_groupby		= "";
8960Syaroslav@ivinco.com			$this->_groupfunc	= SPH_GROUPBY_DAY;
8970Syaroslav@ivinco.com			$this->_groupsort	= "@group desc";
8980Syaroslav@ivinco.com			$this->_groupdistinct = "";
8990Syaroslav@ivinco.com		}
9000Syaroslav@ivinco.com
9010Syaroslav@ivinco.com		/// clear all attribute value overrides (for multi-queries)
9020Syaroslav@ivinco.com		function ResetOverrides()
9030Syaroslav@ivinco.com		{
9040Syaroslav@ivinco.com			$this->_overrides = array();
9050Syaroslav@ivinco.com		}
9060Syaroslav@ivinco.com
9070Syaroslav@ivinco.com		//////////////////////////////////////////////////////////////////////////////
9080Syaroslav@ivinco.com
9090Syaroslav@ivinco.com		/// connect to searchd server, run given search query through given indexes,
9100Syaroslav@ivinco.com		/// and return the search results
9110Syaroslav@ivinco.com		function Query($query, $index = "*", $comment = "")
9120Syaroslav@ivinco.com		{
9130Syaroslav@ivinco.com			assert(empty($this->_reqs));
9140Syaroslav@ivinco.com
9150Syaroslav@ivinco.com			$this->AddQuery($query, $index, $comment);
9160Syaroslav@ivinco.com			$results = $this->RunQueries();
9170Syaroslav@ivinco.com			$this->_reqs = array(); // just in case it failed too early
9180Syaroslav@ivinco.com
9190Syaroslav@ivinco.com			if (!is_array($results))
9200Syaroslav@ivinco.com				return false; // probably network error; error message should be already filled
9210Syaroslav@ivinco.com
9220Syaroslav@ivinco.com			$this->_error = $results[0]["error"];
9230Syaroslav@ivinco.com			$this->_warning = $results[0]["warning"];
9240Syaroslav@ivinco.com			if ($results[0]["status"] == SEARCHD_ERROR)
9250Syaroslav@ivinco.com				return false;
9260Syaroslav@ivinco.com			else
9270Syaroslav@ivinco.com				return $results[0];
9280Syaroslav@ivinco.com		}
9290Syaroslav@ivinco.com
9300Syaroslav@ivinco.com		/// helper to pack floats in network byte order
9310Syaroslav@ivinco.com		function _PackFloat($f)
9320Syaroslav@ivinco.com		{
9330Syaroslav@ivinco.com			$t1 = pack("f", $f); // machine order
9340Syaroslav@ivinco.com			list(, $t2) = unpack("L*", $t1); // int in machine order
9350Syaroslav@ivinco.com			return pack("N", $t2);
9360Syaroslav@ivinco.com		}
9370Syaroslav@ivinco.com
9380Syaroslav@ivinco.com		/// add query to multi-query batch
9390Syaroslav@ivinco.com		/// returns index into results array from RunQueries() call
9400Syaroslav@ivinco.com		function AddQuery($query, $index = "*", $comment = "")
9410Syaroslav@ivinco.com		{
9420Syaroslav@ivinco.com			// mbstring workaround
9430Syaroslav@ivinco.com			$this->_MBPush();
9440Syaroslav@ivinco.com
9450Syaroslav@ivinco.com			// build request
9460Syaroslav@ivinco.com			$req = pack("NNNNN", $this->_offset, $this->_limit, $this->_mode, $this->_ranker, $this->_sort); // mode and limits
9470Syaroslav@ivinco.com			$req .= pack("N", strlen($this->_sortby)) . $this->_sortby;
9480Syaroslav@ivinco.com			$req .= pack("N", strlen($query)) . $query; // query itself
9490Syaroslav@ivinco.com			$req .= pack("N", count($this->_weights)); // weights
9500Syaroslav@ivinco.com			foreach ($this->_weights as $weight)
9510Syaroslav@ivinco.com				$req .= pack("N", (int)$weight);
9520Syaroslav@ivinco.com			$req .= pack("N", strlen($index)) . $index; // indexes
9530Syaroslav@ivinco.com			$req .= pack("N", 1); // id64 range marker
9540Syaroslav@ivinco.com			$req .= sphPackU64($this->_min_id) . sphPackU64($this->_max_id); // id64 range
9550Syaroslav@ivinco.com
9560Syaroslav@ivinco.com			// filters
9570Syaroslav@ivinco.com			$req .= pack("N", count($this->_filters));
9580Syaroslav@ivinco.com			foreach ($this->_filters as $filter) {
9590Syaroslav@ivinco.com				$req .= pack("N", strlen($filter["attr"])) . $filter["attr"];
9600Syaroslav@ivinco.com				$req .= pack("N", $filter["type"]);
9610Syaroslav@ivinco.com				switch ($filter["type"]) {
9620Syaroslav@ivinco.com					case SPH_FILTER_VALUES:
9630Syaroslav@ivinco.com						$req .= pack("N", count($filter["values"]));
9640Syaroslav@ivinco.com						foreach ($filter["values"] as $value)
9650Syaroslav@ivinco.com							$req .= sphPackI64($value);
9660Syaroslav@ivinco.com						break;
9670Syaroslav@ivinco.com
9680Syaroslav@ivinco.com					case SPH_FILTER_RANGE:
9690Syaroslav@ivinco.com						$req .= sphPackI64($filter["min"]) . sphPackI64($filter["max"]);
9700Syaroslav@ivinco.com						break;
9710Syaroslav@ivinco.com
9720Syaroslav@ivinco.com					case SPH_FILTER_FLOATRANGE:
9730Syaroslav@ivinco.com						$req .= $this->_PackFloat($filter["min"]) . $this->_PackFloat($filter["max"]);
9740Syaroslav@ivinco.com						break;
9750Syaroslav@ivinco.com
9760Syaroslav@ivinco.com					default:
9770Syaroslav@ivinco.com						assert(0 && "internal error: unhandled filter type");
9780Syaroslav@ivinco.com				}
9790Syaroslav@ivinco.com				$req .= pack("N", $filter["exclude"]);
9800Syaroslav@ivinco.com			}
9810Syaroslav@ivinco.com
9820Syaroslav@ivinco.com			// group-by clause, max-matches count, group-sort clause, cutoff count
9830Syaroslav@ivinco.com			$req .= pack("NN", $this->_groupfunc, strlen($this->_groupby)) . $this->_groupby;
9840Syaroslav@ivinco.com			$req .= pack("N", $this->_maxmatches);
9850Syaroslav@ivinco.com			$req .= pack("N", strlen($this->_groupsort)) . $this->_groupsort;
9860Syaroslav@ivinco.com			$req .= pack("NNN", $this->_cutoff, $this->_retrycount, $this->_retrydelay);
9870Syaroslav@ivinco.com			$req .= pack("N", strlen($this->_groupdistinct)) . $this->_groupdistinct;
9880Syaroslav@ivinco.com
9890Syaroslav@ivinco.com			// anchor point
9900Syaroslav@ivinco.com			if (empty($this->_anchor)) {
9910Syaroslav@ivinco.com				$req .= pack("N", 0);
9920Syaroslav@ivinco.com			} else {
9930Syaroslav@ivinco.com				$a = &$this->_anchor;
9940Syaroslav@ivinco.com				$req .= pack("N", 1);
9950Syaroslav@ivinco.com				$req .= pack("N", strlen($a["attrlat"])) . $a["attrlat"];
9960Syaroslav@ivinco.com				$req .= pack("N", strlen($a["attrlong"])) . $a["attrlong"];
9970Syaroslav@ivinco.com				$req .= $this->_PackFloat($a["lat"]) . $this->_PackFloat($a["long"]);
9980Syaroslav@ivinco.com			}
9990Syaroslav@ivinco.com
10000Syaroslav@ivinco.com			// per-index weights
10010Syaroslav@ivinco.com			$req .= pack("N", count($this->_indexweights));
10020Syaroslav@ivinco.com			foreach ($this->_indexweights as $idx => $weight)
10030Syaroslav@ivinco.com				$req .= pack("N", strlen($idx)) . $idx . pack("N", $weight);
10040Syaroslav@ivinco.com
10050Syaroslav@ivinco.com			// max query time
10060Syaroslav@ivinco.com			$req .= pack("N", $this->_maxquerytime);
10070Syaroslav@ivinco.com
10080Syaroslav@ivinco.com			// per-field weights
10090Syaroslav@ivinco.com			$req .= pack("N", count($this->_fieldweights));
10100Syaroslav@ivinco.com			foreach ($this->_fieldweights as $field => $weight)
10110Syaroslav@ivinco.com				$req .= pack("N", strlen($field)) . $field . pack("N", $weight);
10120Syaroslav@ivinco.com
10130Syaroslav@ivinco.com			// comment
10140Syaroslav@ivinco.com			$req .= pack("N", strlen($comment)) . $comment;
10150Syaroslav@ivinco.com
10160Syaroslav@ivinco.com			// attribute overrides
10170Syaroslav@ivinco.com			$req .= pack("N", count($this->_overrides));
10180Syaroslav@ivinco.com			foreach ($this->_overrides as $key => $entry) {
10190Syaroslav@ivinco.com				$req .= pack("N", strlen($entry["attr"])) . $entry["attr"];
10200Syaroslav@ivinco.com				$req .= pack("NN", $entry["type"], count($entry["values"]));
10210Syaroslav@ivinco.com				foreach ($entry["values"] as $id => $val) {
10220Syaroslav@ivinco.com					assert(is_numeric($id));
10230Syaroslav@ivinco.com					assert(is_numeric($val));
10240Syaroslav@ivinco.com
10250Syaroslav@ivinco.com					$req .= sphPackU64($id);
10260Syaroslav@ivinco.com					switch ($entry["type"]) {
10270Syaroslav@ivinco.com						case SPH_ATTR_FLOAT:
10280Syaroslav@ivinco.com							$req .= $this->_PackFloat($val);
10290Syaroslav@ivinco.com							break;
10300Syaroslav@ivinco.com						case SPH_ATTR_BIGINT:
10310Syaroslav@ivinco.com							$req .= sphPackI64($val);
10320Syaroslav@ivinco.com							break;
10330Syaroslav@ivinco.com						default:
10340Syaroslav@ivinco.com							$req .= pack("N", $val);
10350Syaroslav@ivinco.com							break;
10360Syaroslav@ivinco.com					}
10370Syaroslav@ivinco.com				}
10380Syaroslav@ivinco.com			}
10390Syaroslav@ivinco.com
10400Syaroslav@ivinco.com			// select-list
10410Syaroslav@ivinco.com			$req .= pack("N", strlen($this->_select)) . $this->_select;
10420Syaroslav@ivinco.com
10430Syaroslav@ivinco.com			// mbstring workaround
10440Syaroslav@ivinco.com			$this->_MBPop();
10450Syaroslav@ivinco.com
10460Syaroslav@ivinco.com			// store request to requests array
10470Syaroslav@ivinco.com			$this->_reqs[] = $req;
10480Syaroslav@ivinco.com			return count($this->_reqs) - 1;
10490Syaroslav@ivinco.com		}
10500Syaroslav@ivinco.com
10510Syaroslav@ivinco.com		/// connect to searchd, run queries batch, and return an array of result sets
10520Syaroslav@ivinco.com		function RunQueries()
10530Syaroslav@ivinco.com		{
10540Syaroslav@ivinco.com			if (empty($this->_reqs)) {
10550Syaroslav@ivinco.com				$this->_error = "no queries defined, issue AddQuery() first";
10560Syaroslav@ivinco.com				return false;
10570Syaroslav@ivinco.com			}
10580Syaroslav@ivinco.com
10590Syaroslav@ivinco.com			// mbstring workaround
10600Syaroslav@ivinco.com			$this->_MBPush();
10610Syaroslav@ivinco.com
10620Syaroslav@ivinco.com			if (!($fp = $this->_Connect())) {
10630Syaroslav@ivinco.com				$this->_MBPop();
10640Syaroslav@ivinco.com				return false;
10650Syaroslav@ivinco.com			}
10660Syaroslav@ivinco.com
10670Syaroslav@ivinco.com			// send query, get response
10680Syaroslav@ivinco.com			$nreqs = count($this->_reqs);
10690Syaroslav@ivinco.com			$req = join("", $this->_reqs);
10700Syaroslav@ivinco.com			$len = 4 + strlen($req);
10710Syaroslav@ivinco.com			$req = pack("nnNN", SEARCHD_COMMAND_SEARCH, VER_COMMAND_SEARCH, $len, $nreqs) . $req; // add header
10720Syaroslav@ivinco.com
10730Syaroslav@ivinco.com			if (
10740Syaroslav@ivinco.com				!($this->_Send($fp, $req, $len + 8)) ||
10750Syaroslav@ivinco.com				!($response = $this->_GetResponse($fp, VER_COMMAND_SEARCH))
10760Syaroslav@ivinco.com			) {
10770Syaroslav@ivinco.com				$this->_MBPop();
10780Syaroslav@ivinco.com				return false;
10790Syaroslav@ivinco.com			}
10800Syaroslav@ivinco.com
10810Syaroslav@ivinco.com			// query sent ok; we can reset reqs now
10820Syaroslav@ivinco.com			$this->_reqs = array();
10830Syaroslav@ivinco.com
10840Syaroslav@ivinco.com			// parse and return response
10850Syaroslav@ivinco.com			return $this->_ParseSearchResponse($response, $nreqs);
10860Syaroslav@ivinco.com		}
10870Syaroslav@ivinco.com
10880Syaroslav@ivinco.com		/// parse and return search query (or queries) response
10890Syaroslav@ivinco.com		function _ParseSearchResponse($response, $nreqs)
10900Syaroslav@ivinco.com		{
10910Syaroslav@ivinco.com			$p = 0; // current position
10920Syaroslav@ivinco.com			$max = strlen($response); // max position for checks, to protect against broken responses
10930Syaroslav@ivinco.com
10940Syaroslav@ivinco.com			$results = array();
10950Syaroslav@ivinco.com			for ($ires = 0; $ires < $nreqs && $p < $max; $ires++) {
10960Syaroslav@ivinco.com				$results[] = array();
10970Syaroslav@ivinco.com				$result = &$results[$ires];
10980Syaroslav@ivinco.com
10990Syaroslav@ivinco.com				$result["error"] = "";
11000Syaroslav@ivinco.com				$result["warning"] = "";
11010Syaroslav@ivinco.com
11020Syaroslav@ivinco.com				// extract status
11030Syaroslav@ivinco.com				list(, $status) = unpack("N*", substr($response, $p, 4));
11040Syaroslav@ivinco.com				$p += 4;
11050Syaroslav@ivinco.com				$result["status"] = $status;
11060Syaroslav@ivinco.com				if ($status != SEARCHD_OK) {
11070Syaroslav@ivinco.com					list(, $len) = unpack("N*", substr($response, $p, 4));
11080Syaroslav@ivinco.com					$p += 4;
11090Syaroslav@ivinco.com					$message = substr($response, $p, $len);
11100Syaroslav@ivinco.com					$p += $len;
11110Syaroslav@ivinco.com
11120Syaroslav@ivinco.com					if ($status == SEARCHD_WARNING) {
11130Syaroslav@ivinco.com						$result["warning"] = $message;
11140Syaroslav@ivinco.com					} else {
11150Syaroslav@ivinco.com						$result["error"] = $message;
11160Syaroslav@ivinco.com						continue;
11170Syaroslav@ivinco.com					}
11180Syaroslav@ivinco.com				}
11190Syaroslav@ivinco.com
11200Syaroslav@ivinco.com				// read schema
11210Syaroslav@ivinco.com				$fields = array();
11220Syaroslav@ivinco.com				$attrs = array();
11230Syaroslav@ivinco.com
11240Syaroslav@ivinco.com				list(, $nfields) = unpack("N*", substr($response, $p, 4));
11250Syaroslav@ivinco.com				$p += 4;
11260Syaroslav@ivinco.com				while ($nfields-- > 0 && $p < $max) {
11270Syaroslav@ivinco.com					list(, $len) = unpack("N*", substr($response, $p, 4));
11280Syaroslav@ivinco.com					$p += 4;
11290Syaroslav@ivinco.com					$fields[] = substr($response, $p, $len);
11300Syaroslav@ivinco.com					$p += $len;
11310Syaroslav@ivinco.com				}
11320Syaroslav@ivinco.com				$result["fields"] = $fields;
11330Syaroslav@ivinco.com
11340Syaroslav@ivinco.com				list(, $nattrs) = unpack("N*", substr($response, $p, 4));
11350Syaroslav@ivinco.com				$p += 4;
11360Syaroslav@ivinco.com				while ($nattrs-- > 0 && $p < $max) {
11370Syaroslav@ivinco.com					list(, $len) = unpack("N*", substr($response, $p, 4));
11380Syaroslav@ivinco.com					$p += 4;
11390Syaroslav@ivinco.com					$attr = substr($response, $p, $len);
11400Syaroslav@ivinco.com					$p += $len;
11410Syaroslav@ivinco.com					list(, $type) = unpack("N*", substr($response, $p, 4));
11420Syaroslav@ivinco.com					$p += 4;
11430Syaroslav@ivinco.com					$attrs[$attr] = $type;
11440Syaroslav@ivinco.com				}
11450Syaroslav@ivinco.com				$result["attrs"] = $attrs;
11460Syaroslav@ivinco.com
11470Syaroslav@ivinco.com				// read match count
11480Syaroslav@ivinco.com				list(, $count) = unpack("N*", substr($response, $p, 4));
11490Syaroslav@ivinco.com				$p += 4;
11500Syaroslav@ivinco.com				list(, $id64) = unpack("N*", substr($response, $p, 4));
11510Syaroslav@ivinco.com				$p += 4;
11520Syaroslav@ivinco.com
11530Syaroslav@ivinco.com				// read matches
11540Syaroslav@ivinco.com				$idx = -1;
11550Syaroslav@ivinco.com				while ($count-- > 0 && $p < $max) {
11560Syaroslav@ivinco.com					// index into result array
11570Syaroslav@ivinco.com					$idx++;
11580Syaroslav@ivinco.com
11590Syaroslav@ivinco.com					// parse document id and weight
11600Syaroslav@ivinco.com					if ($id64) {
11610Syaroslav@ivinco.com						$doc = sphUnpackU64(substr($response, $p, 8));
11620Syaroslav@ivinco.com						$p += 8;
11630Syaroslav@ivinco.com						list(, $weight) = unpack("N*", substr($response, $p, 4));
11640Syaroslav@ivinco.com						$p += 4;
11650Syaroslav@ivinco.com					} else {
11660Syaroslav@ivinco.com						list($doc, $weight) = array_values(unpack(
11670Syaroslav@ivinco.com							"N*N*",
11680Syaroslav@ivinco.com							substr($response, $p, 8)
11690Syaroslav@ivinco.com						));
11700Syaroslav@ivinco.com						$p += 8;
11710Syaroslav@ivinco.com						$doc = sphFixUint($doc);
11720Syaroslav@ivinco.com					}
11730Syaroslav@ivinco.com					$weight = sprintf("%u", $weight);
11740Syaroslav@ivinco.com
11750Syaroslav@ivinco.com					// create match entry
11760Syaroslav@ivinco.com					if ($this->_arrayresult)
11770Syaroslav@ivinco.com						$result["matches"][$idx] = array("id" => $doc, "weight" => $weight);
11780Syaroslav@ivinco.com					else
11790Syaroslav@ivinco.com						$result["matches"][$doc]["weight"] = $weight;
11800Syaroslav@ivinco.com
11810Syaroslav@ivinco.com					// parse and create attributes
11820Syaroslav@ivinco.com					$attrvals = array();
11830Syaroslav@ivinco.com					foreach ($attrs as $attr => $type) {
11840Syaroslav@ivinco.com						// handle 64bit ints
11850Syaroslav@ivinco.com						if ($type == SPH_ATTR_BIGINT) {
11860Syaroslav@ivinco.com							$attrvals[$attr] = sphUnpackI64(substr($response, $p, 8));
11870Syaroslav@ivinco.com							$p += 8;
11880Syaroslav@ivinco.com							continue;
11890Syaroslav@ivinco.com						}
11900Syaroslav@ivinco.com
11910Syaroslav@ivinco.com						// handle floats
11920Syaroslav@ivinco.com						if ($type == SPH_ATTR_FLOAT) {
11930Syaroslav@ivinco.com							list(, $uval) = unpack("N*", substr($response, $p, 4));
11940Syaroslav@ivinco.com							$p += 4;
11950Syaroslav@ivinco.com							list(, $fval) = unpack("f*", pack("L", $uval));
11960Syaroslav@ivinco.com							$attrvals[$attr] = $fval;
11970Syaroslav@ivinco.com							continue;
11980Syaroslav@ivinco.com						}
11990Syaroslav@ivinco.com
12000Syaroslav@ivinco.com						// handle everything else as unsigned ints
12010Syaroslav@ivinco.com						list(, $val) = unpack("N*", substr($response, $p, 4));
12020Syaroslav@ivinco.com						$p += 4;
12030Syaroslav@ivinco.com						if ($type & SPH_ATTR_MULTI) {
12040Syaroslav@ivinco.com							$attrvals[$attr] = array();
12050Syaroslav@ivinco.com							$nvalues = $val;
12060Syaroslav@ivinco.com							while ($nvalues-- > 0 && $p < $max) {
12070Syaroslav@ivinco.com								list(, $val) = unpack("N*", substr($response, $p, 4));
12080Syaroslav@ivinco.com								$p += 4;
12090Syaroslav@ivinco.com								$attrvals[$attr][] = sphFixUint($val);
12100Syaroslav@ivinco.com							}
12110Syaroslav@ivinco.com						} else {
12120Syaroslav@ivinco.com							$attrvals[$attr] = sphFixUint($val);
12130Syaroslav@ivinco.com						}
12140Syaroslav@ivinco.com					}
12150Syaroslav@ivinco.com
12160Syaroslav@ivinco.com					if ($this->_arrayresult)
12170Syaroslav@ivinco.com						$result["matches"][$idx]["attrs"] = $attrvals;
12180Syaroslav@ivinco.com					else
12190Syaroslav@ivinco.com						$result["matches"][$doc]["attrs"] = $attrvals;
12200Syaroslav@ivinco.com				}
12210Syaroslav@ivinco.com
12220Syaroslav@ivinco.com				list($total, $total_found, $msecs, $words) =
12230Syaroslav@ivinco.com					array_values(unpack("N*N*N*N*", substr($response, $p, 16)));
12240Syaroslav@ivinco.com				$result["total"] = sprintf("%u", $total);
12250Syaroslav@ivinco.com				$result["total_found"] = sprintf("%u", $total_found);
12260Syaroslav@ivinco.com				$result["time"] = sprintf("%.3f", $msecs / 1000);
12270Syaroslav@ivinco.com				$p += 16;
12280Syaroslav@ivinco.com
12290Syaroslav@ivinco.com				while ($words-- > 0 && $p < $max) {
12300Syaroslav@ivinco.com					list(, $len) = unpack("N*", substr($response, $p, 4));
12310Syaroslav@ivinco.com					$p += 4;
12320Syaroslav@ivinco.com					$word = substr($response, $p, $len);
12330Syaroslav@ivinco.com					$p += $len;
12340Syaroslav@ivinco.com					list($docs, $hits) = array_values(unpack("N*N*", substr($response, $p, 8)));
12350Syaroslav@ivinco.com					$p += 8;
12360Syaroslav@ivinco.com					$result["words"][$word] = array(
12370Syaroslav@ivinco.com						"docs" => sprintf("%u", $docs),
12380Syaroslav@ivinco.com						"hits" => sprintf("%u", $hits)
12390Syaroslav@ivinco.com					);
12400Syaroslav@ivinco.com				}
12410Syaroslav@ivinco.com			}
12420Syaroslav@ivinco.com
12430Syaroslav@ivinco.com			$this->_MBPop();
12440Syaroslav@ivinco.com			return $results;
12450Syaroslav@ivinco.com		}
12460Syaroslav@ivinco.com
12470Syaroslav@ivinco.com		/////////////////////////////////////////////////////////////////////////////
12480Syaroslav@ivinco.com		// excerpts generation
12490Syaroslav@ivinco.com		/////////////////////////////////////////////////////////////////////////////
12500Syaroslav@ivinco.com
12510Syaroslav@ivinco.com		/// connect to searchd server, and generate exceprts (snippets)
12520Syaroslav@ivinco.com		/// of given documents for given query. returns false on failure,
12530Syaroslav@ivinco.com		/// an array of snippets on success
12540Syaroslav@ivinco.com		function BuildExcerpts($docs, $index, $words, $opts = array())
12550Syaroslav@ivinco.com		{
12560Syaroslav@ivinco.com			assert(is_array($docs));
12570Syaroslav@ivinco.com			assert(is_string($index));
12580Syaroslav@ivinco.com			assert(is_string($words));
12590Syaroslav@ivinco.com			assert(is_array($opts));
12600Syaroslav@ivinco.com
12610Syaroslav@ivinco.com			$this->_MBPush();
12620Syaroslav@ivinco.com
12630Syaroslav@ivinco.com			if (!($fp = $this->_Connect())) {
12640Syaroslav@ivinco.com				$this->_MBPop();
12650Syaroslav@ivinco.com				return false;
12660Syaroslav@ivinco.com			}
12670Syaroslav@ivinco.com
12680Syaroslav@ivinco.com			/////////////////
12690Syaroslav@ivinco.com			// fixup options
12700Syaroslav@ivinco.com			/////////////////
12710Syaroslav@ivinco.com
12720Syaroslav@ivinco.com			if (!isset($opts["before_match"]))		$opts["before_match"] = "<b>";
12730Syaroslav@ivinco.com			if (!isset($opts["after_match"]))			$opts["after_match"] = "</b>";
12740Syaroslav@ivinco.com			if (!isset($opts["chunk_separator"]))		$opts["chunk_separator"] = <